Güvenli bir şekilde zaman uyumsuz bir yöntem C # beklemek olmadan nasıl çağırılır


322

asyncHiçbir veri döndüren bir yöntem var :

public async Task MyAsyncMethod()
{
    // do some stuff async, don't return any data
}

Bu bazı veri döndürür başka bir yöntemden çağırıyorum:

public string GetStringData()
{
    MyAsyncMethod(); // this generates a warning and swallows exceptions
    return "hello world";
}

Arayan MyAsyncMethod()bir "neden beklemeden bu çağrı beklenen olmadığı için, geçerli yöntem çağrısı tamamlanmadan önce çalışmaya devam görsel stüdyoda uyarı". Bu uyarının sayfasında şunu belirtir:

Uyarıyı, yalnızca eşzamansız çağrının tamamlanmasını beklemek istemediğinizden ve çağrılan yöntemin herhangi bir istisna getirmeyeceğinden eminseniz bastırmayı düşünmelisiniz .

Eminim aramanın tamamlanmasını beklemek istemiyorum; Zamana ihtiyacım yok ya da vaktim yok. Ancak çağrı istisnalar doğurabilir .

Bu sorunu birkaç kez tökezledim ve bunun ortak bir çözümü olması gereken yaygın bir sorun olduğuna eminim.

Sonucu beklemeden nasıl async yöntemini güvenle arayabilirim?

Güncelleme:

Sonucu beklediğimi öne süren insanlar için bu, web servisimizdeki (ASP.NET Web API) bir web isteğine yanıt veren koddur. Bir UI bağlamında beklemek UI iş parçacığını serbest tutar, ancak bir web isteği çağrısında beklemek, göreve yanıt vermeden önce Görev'in bitmesini bekler ve böylece yanıt sürelerini hiçbir sebep olmadan arttırır.


Neden sadece bir tamamlama yöntemi oluşturup onu görmezden gelmiyorsunuz? Çünkü arka plan iş parçacığında çalışıyorsa. O zaman programınızın yine de sona ermesini durdurmaz.
Yahya

1
Sonucu beklemek istemiyorsanız, tek seçenek uyarıyı yoksaymak / gizlemektir. Eğer varsa do ardından sonuç / istisna için beklemek istiyorumMyAsyncMethod().Wait()
Peter Ritchie

1
Düzenlemeniz hakkında: bu benim için anlamlı değil. Yanıtın istemden 1 saniye sonra istemciye gönderildiğini ve 2 saniye sonra zaman uyumsuz yönteminizin bir istisna attığını varsayalım. Bu istisna dışında ne yapardınız? Yanıtınız zaten gönderildiyse istemciye gönderemezsiniz. Bununla başka ne yapardın?

2
@Romoku Yeterince adil. Zaten birinin günlüğe baktığını varsayarsak. :)

2
ASP.NET Web API senaryosundaki bir varyasyon, bir isteğin pahalı bir şey yapmak için uzun bir arka plan görevi oluşturduğu, ancak yine de yapmak istediği uzun ömürlü bir süreçte (örneğin, bir Windows hizmeti gibi) kendi kendine barındırılan bir Web API'sidir. HTTP 202 (Kabul Edildi) ile hızlı yanıt alın.
David Rubin

Yanıtlar:


187

İstisnayı "eşzamansız olarak" almak istiyorsanız, şunları yapabilirsiniz:

  MyAsyncMethod().
    ContinueWith(t => Console.WriteLine(t.Exception),
        TaskContinuationOptions.OnlyOnFaulted);

Bu, "ana" iş parçacığı dışındaki bir iş parçacığındaki bir özel durumla başa çıkmanıza olanak tanır. Bu MyAsyncMethod(), çağıran evrenden çağrı beklemek zorunda kalmayacağınız anlamına gelirMyAsyncMethod ; ancak yine de bir istisna dışında bir şey yapmanıza izin verir - ancak bir istisna oluşursa.

Güncelleme:

teknik olarak aşağıdakilere benzer bir şey yapabilirsiniz await:

try
{
    await MyAsyncMethod().ConfigureAwait(false);
}
catch (Exception ex)
{
    Trace.WriteLine(ex);
}

... özellikle try/ catch(veya using) kullanmanız gerektiğinde yararlı olabilir, ancak ContinueWithbunun ne ConfigureAwait(false)anlama geldiğini bilmek zorunda olduğunuz için biraz daha açık olduğunu düşünüyorum .


7
Bunu bir uzantı yöntemine Taskçevirdim: public static class AsyncUtility {public static void PerformAsyncTaskWithoutAwait (bu Görev görevi, Action <Task> exceptionHandler) {var dummy = task.ContinueWith (t => exceptionHandler (t), TaskContinuationOptions.OnlyOnlyOnlyOnlyOnaO; }} Kullanım: MyAsyncMethod (). PerformAsyncTaskWithoutAwait (t => log.ErrorFormat ("MyAsyncMethod çağrılırken bir hata oluştu: \ n {0}", t.Exception));
Mark Avenius

2
downvoter. Yorumlar? Cevapta yanlış bir şey varsa bilmek ve / veya düzeltmek isterim.
Peter Ritchie

2
Hey - İndirmedim, ama ... Güncellemeni açıklayabilir misin? Çağrı beklemiyordu, bu yüzden böyle yaparsanız, çağrı için beklersiniz, sadece yakalanan bağlamda devam
etmezsiniz

1
"bekle" bu bağlamda doğru açıklama değil. ConfiguratAwait(false)Görev tamamlanıncaya kadar devam eden satır yürütülmez, ancak geçerli iş parçacığı bunun için "beklemez" (yani engellemez), sonraki satır çağrıyı beklemeye asenkron olarak çağrılır. Bu olmadan, ConfigureAwait(false)sonraki satır orijinal web isteği bağlamında yürütülür. İle ConfigurateAwait(false)o devam etmek orijinal içerik / iplik kurtararak, zaman uyumsuz yönteminin (görev) ile aynı bağlamda yürütüldüğünde ...
Peter Ritchie

15
ContinueWith sürümü try {await} catch {} sürümüyle aynı değil. İlk sürümde, ContinueWith () öğesinden sonraki her şey hemen yürütülür. İlk görev başlatılır ve unutulur. İkinci versiyonda, catch {} 'den sonraki her şey sadece ilk görev tamamlandıktan sonra yürütülecektir. İkinci versiyon bekliyoruz MyAsyncMethod ()" eşdeğerdir ContinueWith (t => Console.WriteLine (t.Exception), TaskContinuationOptions.OnlyOnFaulted) .ConfigureAwait (FALS).
Thanasis İoannidis

67

Öncelikle GetStringDatabir asyncyöntem yapmayı düşünmeli awaitve görevin geri döndürülmesini sağlamalısınız MyAsyncMethod.

İstisnaları ele almanızın MyAsyncMethod veya ne zaman tamamlandığını bilmeniz gerekmediğinden kesinlikle eminseniz, bunu yapabilirsiniz :

public string GetStringData()
{
  var _ = MyAsyncMethod();
  return "hello world";
}

BTW, bu bir "ortak sorun" değil. Bazı kod icra edilmesini isteyen çok nadirdir ve tamamlar umrumda değil ve başarıyla tamamlanıp tamamlanmadığını umursamak .

Güncelleme:

ASP.NET'te olduğunuz ve erken dönmek istediğiniz için konuyla ilgili blog yayınımı yararlı bulabilirsiniz . Ancak, ASP.NET bunun için tasarlanmamıştır ve garanti yoktur ve yanıt döndükten sonra kodunuzun çalışacağının . ASP.NET çalışmasına izin vermek için elinden geleni yapacaktır, ancak bunu garanti edemez.

Yani, bir olayı bir günlüğe atmak gibi basit bir şey için iyi bir çözümdür, burada burada birkaçını kaybederseniz gerçekten önemli değildir . İş açısından kritik her türlü işlem için iyi bir çözüm değildir. Bu durumlarda, işlemleri kaydetmek için kalıcı bir yolla (örneğin, Azure Kuyrukları, MSMQ) ve bunları işlemek için ayrı bir arka plan işlemiyle (örneğin, Azure Çalışan Rolü, Win32 Hizmeti) daha karmaşık bir mimari benimsemeniz gerekir .


2
Sanırım yanlış anlamış olabilirsiniz. Ben bunu o istisnalar atar ve başarısız olursa bakımı, ama benim veri dönmeden önce yöntemini bekliyor zorunda kalmak istemiyoruz. Ayrıca, herhangi bir fark yaratıyorsa, çalıştığım bağlam hakkındaki düzenlememe bakın.
George Powell

5
@GeorgePowell: Etkin bir istek olmadan bir ASP.NET bağlamında kodun çalışması çok tehlikelidir. Size yardımcı olabilecek bir blog yayınım var , ancak sorununuzu daha fazla bilmeden, bu yaklaşımı tavsiye edip etmeyeceğimi söyleyemem.
Stephen Cleary

@StephenCleary Benzer bir ihtiyacım var. Örneğimde, bulutta çalıştırmak için bir toplu işlem motoruna ihtiyacım var / toplu işlemeyi başlatmak için son noktaya "ping" yapacağım, ancak hemen geri dönmek istiyorum. Ping atmaya başladığından beri, her şeyi oradan halledebilir. Atılan istisnalar varsa, o zaman sadece "BatchProcessLog / Hata" tablolarımda giriş yapardı ...
16:31 ganders

8
C # 7 var _ = MyAsyncMethod();ile değiştirebilirsiniz _ = MyAsyncMethod();. Bu hala CS4014 uyarısını önler, ancak değişkeni kullanmadığınızı biraz daha açık hale getirir.
Brian

48

Peter Ritchie'nin cevabı istediğim şeydi ve Stephen Cleary'nin ASP.NET'te erken dönme hakkındaki makalesi çok yardımcı oldu.

Ancak daha genel bir sorun olarak (ASP.NET içeriğine özgü değildir) aşağıdaki Konsol uygulaması, Peter'ın yanıtını kullanma ve davranışını gösterir Task.ContinueWith(...)

static void Main(string[] args)
{
  try
  {
    // output "hello world" as method returns early
    Console.WriteLine(GetStringData());
  }
  catch
  {
    // Exception is NOT caught here
  }
  Console.ReadLine();
}

public static string GetStringData()
{
  MyAsyncMethod().ContinueWith(OnMyAsyncMethodFailed, TaskContinuationOptions.OnlyOnFaulted);
  return "hello world";
}

public static async Task MyAsyncMethod()
{
  await Task.Run(() => { throw new Exception("thrown on background thread"); });
}

public static void OnMyAsyncMethodFailed(Task task)
{
  Exception ex = task.Exception;
  // Deal with exceptions here however you want
}

GetStringData()Erken beklemeden döner MyAsyncMethod()atılmış ve istisnalar MyAsyncMethod()ele alınmaktadır OnMyAsyncMethodFailed(Task task)ve değil de try/ catchçevresindeGetStringData()


9
Çıkarın Console.ReadLine();ve biraz uyku / gecikme ekleyin MyAsyncMethodve istisnayı asla görmeyeceksiniz.
Kasım'da timtam

17

Sonunda bu çözüm ile:

public async Task MyAsyncMethod()
{
    // do some stuff async, don't return any data
}

public string GetStringData()
{
    // Run async, no warning, exception are catched
    RunAsync(MyAsyncMethod()); 
    return "hello world";
}

private void RunAsync(Task task)
{
    task.ContinueWith(t =>
    {
        ILog log = ServiceLocator.Current.GetInstance<ILog>();
        log.Error("Unexpected Error", t.Exception);

    }, TaskContinuationOptions.OnlyOnFaulted);
}


2

Sanırım soru ortaya çıkıyor, bunu neden yapmanız gerekiyor? asyncC # 5.0'ın nedeni, bir sonuç beklemenizdir. Bu yöntem aslında eşzamansız değildir, sadece geçerli iş parçacığına çok fazla müdahale etmemek için bir seferde çağrılır.

Belki bir iplik başlatmak ve kendi başına bitirmek için bırakmak daha iyi olabilir.


2
asyncbir sonucu "beklemekten" biraz daha fazlasıdır. "await", "await" i takip eden satırların "await" i çağıran aynı evre üzerinde eşzamansız olarak yürütüldüğünü belirtir. Bu kullanım olanağı yanı sıra (tabii ki, "beklemek" olmadan yapılır, ancak delegelerin bir demet sahip sonunda ve kod sıralı görünüm ve his kaybetmek olabilir usingve try/catch...
Peter Ritchie

1
@PeterRitchie İşlevsel olarak eşzamansız, awaitanahtar kelimeyi kullanmayan ve anahtar kelimeyi kullanmayan bir yönteminiz olabilir async, ancak bu yöntemin tanımında asyncda kullanmadan anahtar sözcüğü kullanmanın bir anlamı yoktur await.
13'te

1
@PeterRitchie Şu ifadeden: async"bir sonuç" beklemekten "biraz daha fazlasıdır. asyncAnahtar kelime (bunu ters tırnakların çevrelerdi ile anahtar kelime var zımni) anlamına hiçbir şey daha sonucunu beklemek daha. Genel CS kavramları olarak, bir sonuç beklemekten daha fazlası anlamına gelen eşzamansızlıktır.
Mart'ta Servet

1
@Servy async, zaman uyumsuz yöntemde bekleyen tüm işlemleri yöneten bir durum makinesi oluşturur. awaitYöntem içinde s yoksa, hala bu durum makinesini oluşturur - ancak yöntem asenkron değildir. Ve asyncyöntem geri dönerse void, bekleyecek bir şey yok. Yani, sadece bir sonuç beklemekten daha fazlası.
Peter Ritchie

1
@PeterRitchie Yöntem bir görev döndürdüğü sürece onu bekleyebilirsiniz. Durum makinesine veya asyncanahtar kelimeye gerek yoktur . Yapmanız gereken tek şey (ve bu özel durumda bir durum makinesiyle gerçekten olan her şey), yöntemin senkronize olarak çalıştırılması ve daha sonra tamamlanmış bir göreve sarılmasıdır. Ben teknik olarak sadece devlet makinesini kaldırmak değil sanırım; durum makinesini kaldırır ve ararsınız Task.FromResult. Seni (ve ayrıca derleyici yazarlarını) zeyilnameyi kendi başınıza ekleyebileceğini varsaydım.
13'te

0

İleti döngülerine sahip teknolojilerde (ASP bunlardan biri olup olmadığından emin değilsiniz), görev bitene kadar döngüyü engelleyebilir ve iletileri işleyebilir ve kodun engelini kaldırmak için ContinueWith'i kullanabilirsiniz:

public void WaitForTask(Task task)
{
    DispatcherFrame frame = new DispatcherFrame();
    task.ContinueWith(t => frame.Continue = false));
    Dispatcher.PushFrame(frame);
}

Bu yaklaşım ShowDialog'u engellemeye ve hala kullanıcı arayüzünü duyarlı tutmaya benzer.


0

Burada partiye geç kaldım, ama diğer cevaplarda referans olarak görmediğim harika bir kütüphane var

https://github.com/brminnick/AsyncAwaitBestPractices

"Ateş Et ve Unut" gerekiyorsa görevdeki uzantı yöntemini çağırırsınız.

İstisna eylemini çağrıya geçirmek, her iki dünyanın en iyisini elde etmenizi sağlar - istisnayı zarif bir şekilde ele alma yeteneğini korurken, yürütmeyi beklemenize ve kullanıcılarınızı yavaşlatmanıza gerek yoktur.

Örneğinizde şu şekilde kullanabilirsiniz:

   public string GetStringData()
    {
        MyAsyncMethod().SafeFireAndForget(onException: (exception) =>
                    {
                      //DO STUFF WITH THE EXCEPTION                    
                    }); 
        return "hello world";
    }

Ayrıca MVVM Xamarin çözümüm için harika olan ICommand'ı uygulayan beklenen AsyncCommands veriyor


-1

Çözüm, HttpClient'i sincronizasyon bağlamı olmadan başka bir yürütme görevine başlatmaktır:

var submit = httpClient.PostAsync(uri, new StringContent(body, Encoding.UTF8,"application/json"));
var t = Task.Run(() => submit.ConfigureAwait(false));
await t.ConfigureAwait(false);

-1

Bunu gerçekten yapmak istiyorsan. Sadece "beklemeden C # içinde bir zaman uyumsuz yöntemi çağırmak" adreslemek için, içinde zaman uyumsuz yöntemi çalıştırabilirsiniz Task.Run. Bu yaklaşım bitene kadar bekleyecek MyAsyncMethod.

public string GetStringData()
{
    Task.Run(()=> MyAsyncMethod()).Result;
    return "hello world";
}

awaitResultgörevinizin zaman uyumsuz olarak açılmasını sağlarken, yalnızca Sonucu kullanmak görev tamamlanıncaya kadar engellenir.


-1

Genellikle async yöntemi Task sınıfını döndürür. Eğer kullanırsanız Wait()yöntem veya Resultözellik ve kod istisna atar - istisna tipi içine sarılmış olur AggregateException- o zaman sorguya gerekException.InnerException doğru istisna bulmak için.

Ama aynı zamanda kullanmak da mümkün .GetAwaiter().GetResult() yerine - aynı zamanda zaman uyumsuz görev bekler, ancak istisna sarmaz.

İşte kısa bir örnek:

public async Task MyMethodAsync()
{
}

public string GetStringData()
{
    MyMethodAsync().GetAwaiter().GetResult();
    return "test";
}

Ayrıca, async işlevinden bazı parametreler döndürmek isteyebilirsiniz - bu, Action<return type>örneğin async işlevine ekstra sağlayarak elde edilebilir , örneğin:

public string GetStringData()
{
    return MyMethodWithReturnParameterAsync().GetAwaiter().GetResult();
}

public async Task<String> MyMethodWithReturnParameterAsync()
{
    return "test";
}

Eşzamansız yöntemlerin ASync, yalnızca aynı ada sahip eşitleme işlevleri arasında çarpışmayı önlemek için, genellikle sonek adlarının bulunduğunu lütfen unutmayın . (Örn. FileStream.ReadAsync) - Bu öneriye uymak için işlev adlarını güncelledim.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.