C # 'da eşzamansız bir yöntem nasıl oluşturulur?


196

Okuduğum her blog yazısı size C # 'da eşzamansız bir yöntemi nasıl kullanacağınızı anlatıyor, ancak bazı garip nedenlerden dolayı tüketmek için kendi eşzamansız yöntemlerinizi nasıl oluşturacağınızı asla açıklamayın. Bu yüzden şu anda benim yöntem tüketen bu kodu var:

private async void button1_Click(object sender, EventArgs e)
{
    var now = await CountToAsync(1000);
    label1.Text = now.ToString();
}

Ve ben şu yöntemi yazdım CountToAsync:

private Task<DateTime> CountToAsync(int num = 1000)
{
    return Task.Factory.StartNew(() =>
    {
        for (int i = 0; i < num; i++)
        {
            Console.WriteLine("#{0}", i);
        }
    }).ContinueWith(x => DateTime.Now);
}

Bu, Task.Factoryzaman uyumsuz bir yöntem yazmanın en iyi yolu mu yoksa başka bir yolla mı yazmalıyım?


22
Bir yöntemin nasıl yapılandırılacağı hakkında genel bir soru soruyorum. Sadece eşzamanlı yöntemlerimi eşzamansız yöntemlere dönüştürmeye nereden başlayacağımı bilmek istiyorum.
Khalid Abuhakmeh

2
Peki, tipik bir eşzamanlı yöntem ne işe yarıyor ve neden eşzamansız hale getirmek istiyorsunuz ?
Eric Lippert

Diyelim ki bir grup dosyayı toplu olarak işlemek ve bir sonuç nesnesi döndürmek zorundayım.
Khalid Abuhakmeh

1
Tamam, öyleyse: (1) yüksek gecikmeli işlem nedir: dosyaları elde etmek - çünkü ağ yavaş olabilir ya da her neyse - ya da işlem yapıyor - CPU yoğun olduğu için, diyelim. Ve (2) hala neden ilk etapta asenkron olmasını istediğinizi söylemediniz. Engellemek istemediğiniz bir UI iş parçacığı var mı?
Eric Lippert

21
@EricLippert Op tarafından verilen örnek çok basit, gerçekten bu kadar karmaşık olması gerekmiyor.
David B.

Yanıtlar:


227

Bu StartNewkarmaşıklık düzeyine ihtiyacınız yoksa tavsiye etmiyorum .

Eşzamansız yönteminiz diğer eşzamansız yöntemlere bağımlıysa, en kolay yaklaşım şu asyncanahtar sözcüğü kullanmaktır :

private static async Task<DateTime> CountToAsync(int num = 10)
{
  for (int i = 0; i < num; i++)
  {
    await Task.Delay(TimeSpan.FromSeconds(1));
  }

  return DateTime.Now;
}

Eşzamansız yönteminiz CPU işi yapıyorsa, şunları kullanmalısınız Task.Run:

private static async Task<DateTime> CountToAsync(int num = 10)
{
  await Task.Run(() => ...);
  return DateTime.Now;
}

async/ awaitİntro'yu yararlı bulabilirsiniz .


10
@Stephen: "Eşzamansız yönteminiz diğer eşzamansız yöntemlere bağımlıysa" - tamam, ama durum böyle değilse. Ya BeginInvoke'u çağıran bir kodu geri arama koduyla sarmaya çalışsaydı?
Ricibob

1
@Ricibob: Sen kullanmalıdır TaskFactory.FromAsyncsarmak için BeginInvoke. "Geri arama kodu" hakkında ne demek istediğinizden emin değilim; kendi sorunuzu kodla göndermekten çekinmeyin.
Stephen Cleary

@Stephen: Teşekkürler - evet TaskFactory.FromAsync aradığım şeydi.
Ricibob

1
Stephen, for döngüsünde, bir sonraki yineleme hemen mı yoksa Task.Delaygeri dönüşlerden sonra mı çağrılır ?
jp2code

3
@ jp2code: await"eşzamansız bir bekleme" dir, bu nedenle görev tarafından döndürülene kadar bir sonraki yinelemeye geçmezTask.Delay tamamlanıncaya .
Stephen Cleary

12

Yönteminizin içinde async / await kullanmak istemiyor, ancak yine de await anahtar kelimesini dışarıdan kullanabilmek için "dekore" ediyorsanız, TaskCompletionSource.cs :

public static Task<T> RunAsync<T>(Func<T> function)
{ 
    if (function == null) throw new ArgumentNullException(“function”); 
    var tcs = new TaskCompletionSource<T>(); 
    ThreadPool.QueueUserWorkItem(_ =>          
    { 
        try 
        {  
           T result = function(); 
           tcs.SetResult(result);  
        } 
        catch(Exception exc) { tcs.SetException(exc); } 
   }); 
   return tcs.Task; 
}

Buradan ve buradan

Görevler ile böyle bir paradigmayı desteklemek için, Görev cephesini muhafaza etmenin ve keyfi olarak eşzamansız bir işlemi Görev olarak ifade edebilme, ancak bu Görevin ömrünü sağlayan temel altyapının kurallarına göre kontrol edebilmemiz gerekir. zaman uyumsuzluk ve bunu önemli ölçüde maliyetsiz bir şekilde yapmak. TaskCompletionSource'un amacı budur.

Ben de gördüm. NET kaynak örneğin kullanılır. WebClient.cs :

    [HostProtection(ExternalThreading = true)]
    [ComVisible(false)]
    public Task<string> UploadStringTaskAsync(Uri address, string method, string data)
    {
        // Create the task to be returned
        var tcs = new TaskCompletionSource<string>(address);

        // Setup the callback event handler
        UploadStringCompletedEventHandler handler = null;
        handler = (sender, e) => HandleCompletion(tcs, e, (args) => args.Result, handler, (webClient, completion) => webClient.UploadStringCompleted -= completion);
        this.UploadStringCompleted += handler;

        // Start the async operation.
        try { this.UploadStringAsync(address, method, data, tcs); }
        catch
        {
            this.UploadStringCompleted -= handler;
            throw;
        }

        // Return the task that represents the async operation
        return tcs.Task;
    }

Son olarak, aşağıdakileri de yararlı buldum:

Bu soruyu her zaman soruyorum. Bunun anlamı, dış kaynağa giden G / Ç çağrısını engelleyen bir yerde bir iş parçacığı olması gerektiğidir. Yani, eşzamansız kod istek iş parçacığını serbest bırakır, ancak yalnızca sistemin başka bir yerinde başka bir iş parçacığı pahasına, değil mi? Hayır, hiç de değil. Eşzamansız isteklerin neden ölçeklendiğini anlamak için, eşzamansız bir G / Ç çağrısının (basitleştirilmiş) bir örneğini izleyeceğim. Diyelim ki bir istek bir dosyaya yazmalı. İstek iş parçacığı, zaman uyumsuz yazma yöntemini çağırır. WriteAsync, Temel Sınıf Kütüphanesi (BCL) tarafından uygulanır ve eşzamansız G / Ç için tamamlama bağlantı noktalarını kullanır. Bu nedenle, WriteAsync çağrısı, zaman uyumsuz bir dosya yazma olarak işletim sistemine aktarılır. İşletim sistemi daha sonra sürücü yığını ile iletişim kurarak bir G / Ç istek paketinde (IRP) yazmak için verileri iletir. İşlerin ilginç olduğu yer burası: Bir aygıt sürücüsü bir IRP'yi hemen işleyemezse, eşzamansız olarak işlemesi gerekir. Böylece, sürücü diske yazmaya başlamasını söyler ve işletim sistemine "beklemede" yanıtını döndürür. İşletim Sistemi, bu "beklemede" yanıtı BCL'ye iletir ve BCL, istek işleme koduna eksik bir görev döndürür. İstek işleme kodu, bu yöntemden eksik bir görev döndüren görevi bekler. Son olarak, istek işleme kodu tamamlanmamış bir görevi ASP.NET'e geri döndürür ve istek iş parçacığı iş parçacığı havuzuna dönmek için serbest bırakılır. İstek işleme kodu, bu yöntemden eksik bir görev döndüren görevi bekler. Son olarak, istek işleme kodu tamamlanmamış bir görevi ASP.NET'e geri döndürür ve istek iş parçacığı iş parçacığı havuzuna dönmek için serbest bırakılır. İstek işleme kodu, bu yöntemden eksik bir görev döndüren görevi bekler. Son olarak, istek işleme kodu tamamlanmamış bir görevi ASP.NET'e geri döndürür ve istek iş parçacığı iş parçacığı havuzuna dönmek için serbest bırakılır.

ASP.NET'te Async / Await'e Giriş

Hedef ölçeklenebilirliği (yanıt vermekten ziyade) artırmaksa, hepsi bunu yapma fırsatı sağlayan harici bir G / Ç'nin varlığına dayanır.


1
Task.Run uygulamasının üstündeki açıklamayı görürseniz, örneğin , belirtilen çalışmayı ThreadPool üzerinde çalışacak şekilde sıralar ve bu iş için bir Görev tanıtıcısı döndürür . Aslında aynısını yapıyorsun. Eminim Task.Run daha iyi olacak, dahili CancelationToken yönetmek neden ve zamanlama ve çalıştırma seçenekleri ile bazı optimizasyonlar yapar.
unsafePtr

ThreadPool.QueueUserWorkItem ile ne yaptınız Task.Run ile yapılabilir, değil mi?
Razvan

-1

Bir yöntemi zaman uyumsuz hale getirmenin çok basit bir yolu Task.Yield () yöntemini kullanmaktır. MSDN'in belirttiği gibi:

Await Task.Yield (); yöntemi eşzamansız olarak tamamlamaya zorlamak için eşzamansız bir yöntemde.

Yöntemin başlangıcına yerleştirin, hemen arayana geri dönecek ve yöntemin geri kalanını başka bir iş parçacığında tamamlayacaktır.

private async Task<DateTime> CountToAsync(int num = 1000)
{
    await Task.Yield();
    for (int i = 0; i < num; i++)
    {
        Console.WriteLine("#{0}", i);
    }
    return DateTime.Now;
}

WinForms uygulamasında, yöntemin geri kalanı aynı iş parçacığında (UI iş parçacığı) tamamlanır. Task.Yield()Döndürülen sağlamak istiyoruz durumlar için gerçekten yararlıdır Taskhemen oluşturulduktan sonra tamamlanmış olmayacaktır.
Theodor Zoulias
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.