Task.Run'u zaman uyumsuz hale getirmek için bir yönteme mi koymak zorundasınız?


304

Asenkronun en basit haliyle beklediğini anlamaya çalışıyorum. Bu örnek uğruna iki sayı ekleyen çok basit bir yöntem oluşturmak istiyorum, verilmiş, hiç işlem süresi yok, sadece burada bir örnek formüle etme meselesi.

örnek 1

private async Task DoWork1Async()
{
    int result = 1 + 2;
}

ÖRNEK 2

private async Task DoWork2Async()
{
    Task.Run( () =>
    {
        int result = 1 + 2;
    });
}

Ben beklersem DoWork1Async()kod senkronize veya senkronize olmayan çalışır mı?

Task.RunUI iş parçacığını engellememek için yöntemi beklenebilir ve asenkron hale getirmek için senkronizasyon kodunu sarmam gerekir mi?

Benim yöntem bir Taskveya döner olup olmadığını anlamaya çalışıyorum Task<T>ben Task.Runeşzamansız yapmak için kodu sarmak gerekir .

Aptal soru eminim ama insanların içinde bir şey asenk vardır ve sarılmış değil kod bekleyen net örneklere bakın Task.Runya StartNew.


30
İlk snippet'iniz size herhangi bir uyarı vermiyor mu?
svick

Yanıtlar:


587

İlk olarak, bazı terminolojileri temizleyelim: "asenkron" ( async), başlamadan önce çağıran iş parçacığına geri denetim sağlayabileceği anlamına gelir. Bir asyncyöntemde, bu "verim" noktaları awaitifadelerdir.

MSDN belgeleri tarafından "arka plan iş parçacığında yürütülür" anlamında yıllardır kullanılan (mis) olarak "asenkron" teriminden çok farklıdır.

Sorunu daha da karıştırmak, async"beklenebilir" den çok farklıdır; asyncdönüş türleri beklenmeyen bazı yöntemler ve beklenmeyen türleri döndüren birçok yöntem vardır async.

Onlar neyi kadar yeter değildir ; işte budur şunlardır :

  • asyncAnahtar kelime (olduğunu, bu sağlayan bir asenkron metot verir awaitifadeler). asyncyöntemler döndürebilir Task, Task<T>(Gerekirse) veya void.
  • Belirli bir modeli izleyen herhangi bir tür beklenebilir. En yaygın beklenen tipler Taskve Task<T>.

Bu nedenle, sorunuzu "bir işlemi bir arka plan iş parçacığında beklenen şekilde nasıl çalıştırabilirim" olarak yeniden biçimlendirirsek , yanıt şu şekildedir Task.Run:

private Task<int> DoWorkAsync() // No async because the method does not need await
{
  return Task.Run(() =>
  {
    return 1 + 2;
  });
}

(Ancak bu model kötü bir yaklaşımdır; aşağıya bakınız).

Ancak sorunuz " asyncengelleme yerine arayanına nasıl geri dönebilecek bir yöntem oluştururum " ise, yanıt yöntemi bildirmek asyncve await"sağlama" noktaları için kullanmaktır :

private async Task<int> GetWebPageHtmlSizeAsync()
{
  var client = new HttpClient();
  var html = await client.GetAsync("http://www.example.com/");
  return html.Length;
}

Yani, şeylerin temel paterni async, awaitifadelerinde “beklenebilir” olan koda bağımlı olmaktır . Bu "beklenenler" diğer asyncyöntemler veya sadece beklenenleri döndüren normal yöntemler olabilir. Dönen Düzenli yöntemler Task/ Task<T> edebilirsiniz kullanmak Task.Run, bir arka plan iş parçacığı üzerinde kod çalıştırmak için, ya da (daha yaygın) kullanabilecekleri TaskCompletionSource<T>veya kısayolları (biri TaskFactory.FromAsync, Task.FromResultvs). Ben yok bütün bir yöntem içinde sarma tavsiye Task.Run; eşzamanlı yöntemlerin eşzamanlı imzaları olmalıdır ve aşağıdakilere sarılıp sarılmayacağı tüketiciye bırakılmalıdır Task.Run:

private int DoWork()
{
  return 1 + 2;
}

private void MoreSynchronousProcessing()
{
  // Execute it directly (synchronously), since we are also a synchronous method.
  var result = DoWork();
  ...
}

private async Task DoVariousThingsFromTheUIThreadAsync()
{
  // I have a bunch of async work to do, and I am executed on the UI thread.
  var result = await Task.Run(() => DoWork());
  ...
}

Bir var async/ awaitintro blogumda; sonunda bazı iyi takip kaynakları vardır. İçin MSDN belgeleri asyncde alışılmadık derecede iyi.


8
@sgnsajgon: Evet. asyncyöntemler dönmelidir Task, Task<T>ya void. Taskve Task<T>bekliyoruz; voiddeğil.
Stephen Cleary

3
Aslında, bir async voidyöntem imzası derlenecek, işaretçinizi zaman uyumsuz görevinize
kaybettiğiniz

4
@TopinFrassi: Evet, derleyecekler, ancak voidbeklenemezler.
Stephen Cleary

4
@ohadinho: Hayır, blog gönderisinde bahsettiğim şey, tüm yöntemin sadece bir çağrı olması Task.Run( DoWorkAsyncbu cevaptaki gibi ). Kullanımı Task.Runiçin çağrı UI bağlamında bir yöntem (gibi uygun DoVariousThingsFromTheUIThreadAsync).
Stephen Cleary

2
Evet kesinlikle. Kullanımı geçerli olduğundan Task.Runhiç çağırmak bir yöntem, ancak varsa Task.Runbir anti-desen var sonra yöntemin kod, tüm çevresinde (veya hemen hemen tüm) - sadece senkron bu yöntemi tutmak ve taşımak Task.Runbir seviye yukarı.
Stephen Cleary

22

Bir yöntemi zaman uyumsuz olarak dekore ederken hatırlanması gereken en önemli şeylerden biri, en azından yöntemin içinde bir bekleyen operatörün olmasıdır. Örneğinizde, TaskCompletionSource kullanarak aşağıda gösterildiği gibi çeviririm .

private Task<int> DoWorkAsync()
{
    //create a task completion source
    //the type of the result value must be the same
    //as the type in the returning Task
    TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
    Task.Run(() =>
    {
        int result = 1 + 2;
        //set the result to TaskCompletionSource
        tcs.SetResult(result);
    });
    //return the Task
    return tcs.Task;
}

private async void DoWork()
{
    int result = await DoWorkAsync();
}

26
Neden sadece Task.Run () yöntemi tarafından döndürülen görevi döndürmek yerine (ve sonucu döndürmek için gövdesini değiştirmek yerine) TaskCompletionSource kullanıyorsunuz?
ironik

4
Sadece bir yan not. "Eşzamansız boşluk" imzası olan bir yöntem genellikle kötü uygulamadır ve UI kilitlenmesine oldukça kolay yol açabileceğinden kötü kod olarak kabul edilir. Ana istisna, zaman uyumsuz olay işleyicilerdir.
Jazzeroki

12

Bir yöntemi çalıştırmak için Task.Run kullandığınızda, Görev o yöntemi çalıştırmak için threadpool bir iş parçacığı alır. Bu yüzden UI iş parçacığının bakış açısından, UI iş parçacığını engellemediğinden "asenkron" olur.Bu, kullanıcı etkileşimlerine dikkat etmek için genellikle birçok iş parçacığına ihtiyacınız olmadığı için masaüstü uygulaması için iyidir.

Ancak, web uygulaması için her bir talebe bir iş parçacığı havuzu iş parçacığı tarafından hizmet edilir ve böylece bu tür iş parçacıkları kaydedilerek etkin isteklerin sayısı artırılabilir. Sıklıkla eşzamansız işlemi simüle etmek için threadpool iş parçacıklarını kullanmak web uygulamaları için ölçeklenebilir değildir.

True Async, dosya / DB erişimi vb. Gibi G / Ç işlemleri için bir iş parçacığı kullanmayı gerektirmez. G / Ç işleminin neden iş parçacığı gerektirmediğini anlamak için bunu okuyabilirsiniz. http://blog.stephencleary.com/2013/11/there-is-no-thread.html

Basit örneğinizde, saf bir CPU'ya bağlı hesaplamadır, bu yüzden Task.Run kullanmak iyidir.


Bir web api denetleyicisi içinde bir eşzamanlı dış api tüketmek zorunda, bu yüzden Task.Run () senkron çağrıyı sarmak gerekir? Söylediğiniz gibi, ilk istek iş parçacığının engelini kaldırır ama harici API çağırmak için başka bir havuz iş parçacığı kullanıyor. Aslında hala iyi bir fikir olduğunu düşünüyorum çünkü bu şekilde yapıyor teorik olarak birçok istekleri işlemek için iki havuz iş parçacığı kullanabilirsiniz örneğin bir iş parçacığı birçok gelen istekleri işleyebilir ve başka bir tüm bu istekleri için harici api çağırabilir?
stt106

Katılıyorum.Tamk.Run () içindeki tüm senkronize çağrıları kesinlikle sarmamalısınız demiyorum. Sadece potansiyel bir konuyu işaret ediyorum.
zheng yu

1
@ stt106 I should NOT wrap the synchronous call in Task.Run()doğru. Bunu yaparsanız, sadece konuları değiştirirsiniz. yani, ilk istek iş parçacığının engellemesini kaldırıyorsunuz ancak iş parçacığı havuzundan başka bir isteği işlemek için kullanılabilecek başka bir iş parçacığı alıyorsunuz. Tek sonuç, arama kesinlikle sıfır kazanç için tamamlandığında bir bağlamsal geçiş
yüküdür
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.