Kilitlenmeye neden olan zaman uyumsuz / bekleme örneği


97

C # 's async/ awaitanahtar sözcüklerini kullanarak eşzamansız programlama için bazı en iyi uygulamalarla karşılaştım (c # 5.0'da yeniyim).

Verilen tavsiyelerden biri şöyleydi:

Kararlılık: Senkronizasyon bağlamlarınızı bilin

... Bazı senkronizasyon bağlamları evresel olmayan ve tek iş parçacıklıdır. Bu, bağlamda belirli bir zamanda yalnızca bir iş biriminin yürütülebileceği anlamına gelir. Buna bir örnek, Windows UI iş parçacığı veya ASP.NET istek bağlamıdır. Bu tek iş parçacıklı senkronizasyon bağlamlarında, kendinizi kilitlemek kolaydır. Tek iş parçacıklı bir bağlamdan bir görev oluşturursanız, bu görevi bağlamda bekleyin, bekleme kodunuz arka plan görevini engelliyor olabilir.

public ActionResult ActionAsync()
{
    // DEADLOCK: this blocks on the async task
    var data = GetDataAsync().Result;

    return View(data);
}

private async Task<string> GetDataAsync()
{
    // a very simple async method
    var result = await MyWebService.GetDataAsync();
    return result.ToString();
}

Kendim incelemeye çalışırsam, ana iş parçacığı yeni bir tanesine doğar MyWebService.GetDataAsync();, ancak ana iş parçacığı orada beklediği için sonucu bekler GetDataAsync().Result. Bu arada, verilerin hazır olduğunu söyleyin. Ana iş parçacığı neden devam mantığına devam etmiyor ve bundan bir dize sonucu döndürmüyor GetDataAsync()?

Biri bana yukarıdaki örnekte neden bir kilitlenme olduğunu açıklayabilir mi? Sorunun ne olduğu konusunda hiçbir fikrim yok ...


GetDataAsync'in işlerini bitirdiğinden gerçekten emin misiniz? Ya da sadece kilitlenmeye neden olur ve kilitlenmeye neden olmaz?
Andrey

Sağlanan örnek budur. Anladığım kadarıyla, her şeyi bitirmeli ve bir çeşit sonucu hazır olmalı ...
Dror Weiss

4
Neden görevi bekliyorsun? Bunun yerine beklemelisiniz çünkü temelde zaman uyumsuz modelin tüm avantajlarını kaybettiniz.
Toni Petrina

@ ToniPetrina'nın noktasına eklemek, kilitlenme sorunu olmasa bile, engellememeniz gereken bir bağlamda asla yapılmaması var data = GetDataAsync().Result;gereken bir kod satırıdır (UI veya ASP.NET isteği). Kilitlenmese bile, iş parçacığını belirsiz bir süre bloke ediyor. Yani temelde korkunç bir örnek. [Böyle bir kod çalıştırmadan önce UI iş parçacığından çıkmanız veya Toni'nin önerdiği gibi orada da kullanmanız gerekir .]await
ToolmakerSteve

Yanıtlar:


82

Bu örneğe bir bakın , Stephen'ın size net bir cevabı var:

Dolayısıyla, en üst düzey yöntemden başlayarak olan şey budur ( Button1_ClickUI / MyController.GetASP.NET için):

  1. En üst düzey yöntem çağrıları GetJsonAsync(UI / ASP.NET bağlamı içinde).

  2. GetJsonAsyncREST isteğini arayarak başlatır HttpClient.GetStringAsync(hala bağlam içinde).

  3. GetStringAsyncTaskREST isteğinin tamamlanmadığını gösteren tamamlanmamış bir döndürür .

  4. GetJsonAsyncTasktarafından iade edilenleri bekliyor GetStringAsync. Bağlam yakalanır ve GetJsonAsyncyöntemi daha sonra çalıştırmaya devam etmek için kullanılır . yöntemin GetJsonAsynctamamlanmadığını Taskgösteren bir tamamlanmadı döndürür GetJsonAsync.

  5. En üst düzey yöntem, Taskdöndürülen GetJsonAsync. Bu bağlam iş parçacığını engeller.

  6. ... Eninde sonunda, REST isteği tamamlanacaktır. Bu Task, tarafından iade edileni tamamlar GetStringAsync.

  7. Devamı GetJsonAsyncartık çalışmaya hazırdır ve bağlam içinde yürütülebilmesi için bağlamın kullanılabilir olmasını bekler.

  8. Kilitlenme . En üst düzey yöntem, bağlam iş parçacığını engelliyor, GetJsonAsynctamamlanmasını bekliyor ve tamamlanması GetJsonAsynciçin bağlamın serbest olmasını bekliyor. UI örneği için, "bağlam" UI bağlamıdır; ASP.NET örneği için "bağlam", ASP.NET istek bağlamıdır. Bu tür bir kilitlenme "bağlam" için neden olabilir.

Okumanız gereken başka bir bağlantı: Bekleyin ve kullanıcı arayüzü ve kilitlenmeler! Aman!


23
  • Gerçek 1: GetDataAsync().Result;tarafından döndürülen görev GetDataAsync()tamamlandığında çalışır, bu arada UI iş parçacığını engeller
  • Gerçek 2: await ( return result.ToString()) işleminin devamı, yürütme için UI iş parçacığında kuyruğa alınır
  • Gerçek 3: tarafından döndürülen görev GetDataAsync(), sıraya alınmış devamı çalıştırıldığında tamamlanacaktır
  • Gerçek 4: UI iş parçacığı engellendiğinden (Gerçek 1) sıraya alınmış devam ettirme asla çalıştırılmaz

Çıkmaz!

Çıkmaz, Gerçek 1 veya Gerçek 2'den kaçınmak için sunulan alternatiflerle ortadan kaldırılabilir.

  • 1,4 kaçının. UI iş parçacığını engellemek yerine, UI iş var data = await GetDataAsync()parçacığının çalışmaya devam etmesini sağlayan kullanın
  • 2,3'ten kaçının. Bir iş parçacığı var data = Task.Run(GetDataAsync).Resultevresinin senkronizasyon bağlamına devamı gönderecek olan örn . Use gibi bloke edilmemiş farklı bir evreye beklemenin devamını sıraya koyun. Bu, tarafından döndürülen görevin GetDataAsync()tamamlanmasını sağlar.

Bu, Stephen Toub'un örneğini kullandığı bir makalede gerçekten iyi açıklanmıştır DelayAsync().


Gelecek olursak, var data = Task.Run(GetDataAsync).Resultbu benim için yeni. Her zaman dış .Resulttarafın ilk bekleme GetDataAsyncvurulduğu anda hazır olacağını düşünmüşümdür , bu yüzden dataher zaman olacaktır default. İlginç.
nawfal

19

Bir ASP.NET MVC projesinde yine bu sorunla uğraşıyordum. Aramak istediğiniz zaman asyncbir gelen yöntemler PartialView, vermen gereken izin yok PartialView async. Eğer yaparsan bir istisna alırsın.

asyncBir eşitleme yönteminden bir yöntem çağırmak istediğiniz senaryoda aşağıdaki basit geçici çözümü kullanabilirsiniz :

  1. Aramadan önce, SynchronizationContext
  2. Aramayı yap, burada artık kilitlenme olmayacak, bitmesini bekle
  3. Geri yükle SynchronizationContext

Misal:

public ActionResult DisplayUserInfo(string userName)
{
    // trick to prevent deadlocks of calling async method 
    // and waiting for on a sync UI thread.
    var syncContext = SynchronizationContext.Current;
    SynchronizationContext.SetSynchronizationContext(null);

    //  this is the async call, wait for the result (!)
    var model = _asyncService.GetUserInfo(Username).Result;

    // restore the context
    SynchronizationContext.SetSynchronizationContext(syncContext);

    return PartialView("_UserInfo", model);
}

3

Diğer bir ana nokta ise, Görevleri engellememeniz ve kilitlenmeleri önlemek için asenkron kullanmamanız gerektiğidir. O zaman eşzamanlı değil, eşzamanlı engelleme olacaktır.

public async Task<ActionResult> ActionAsync()
{

    var data = await GetDataAsync();

    return View(data);
}

private async Task<string> GetDataAsync()
{
    // a very simple async method
    var result = await MyWebService.GetDataAsync();
    return result.ToString();
}

6
Görev bitene kadar ana (UI) iş parçacığının engellenmesini istersem ne olur ? Veya örneğin bir Konsol uygulamasında? Diyelim ki sadece zaman uyumsuzluğu destekleyen HttpClient kullanmak istiyorum ... Kilitlenme riski olmadan eşzamanlı olarak nasıl kullanabilirim ? Bu mümkün olmalı . WebClient bu şekilde kullanılabiliyorsa (senkronizasyon yöntemlerine sahip olduğu için) ve mükemmel çalışıyorsa, o zaman neden HttpClient ile de yapılamadı?
Dexter

Yukarıda Philip Ngan'ın cevabına bakın (bunun bu yorumdan sonra gönderildiğini biliyorum): Beklemenin devamını engellenmemiş farklı bir iş parçacığına sıralayın, örneğin var data = Task.Run (GetDataAsync) kullanın.Sonuç
Jeroen

@Dexter - re " Görev bitene kadar ana (UI) iş parçacığının engellenmesini istiyorsam ? " - UI iş parçacığının gerçekten engellenmesini istiyor musunuz, yani kullanıcının hiçbir şey yapamayacağı, iptal bile edemeyeceği anlamına mı geliyor? içinde bulunduğunuz yönteme devam etmek istemiyor musunuz? "await" veya "Task.ContinueWith" ikinci durumu ele alın.
ToolmakerSteve

@ToolmakerSteve tabii ki yönteme devam etmek istemiyorum. Ama sadece edemez HttpClient içinde çağrılır - Yol ya tüm zaman uyumsuz kullanamazsınız çünkü bekliyoruz kullanmak ana elbette zaman uyumsuz olamaz, hangi. Sonra bir konsol uygulamasında bütün bunları yapıyorsun sözü - bu durumda tam olarak eski istiyoruz - hatta Uygulamamı istemiyorum olması çok kanallı. Her şeyi engelleyin .
Dexter

-1

Bulduğum bir çalışma Join, sonucu sormadan önce görevde bir uzatma yöntemi kullanmaktır .

Kod şöyle görünüyor:

public ActionResult ActionAsync()
{
  var task = GetDataAsync();
  task.Join();
  var data = task.Result;

  return View(data);
}

Birleştirme yönteminin olduğu yerler:

public static class TaskExtensions
{
    public static void Join(this Task task)
    {
        var currentDispatcher = Dispatcher.CurrentDispatcher;
        while (!task.IsCompleted)
        {
            // Make the dispatcher allow this thread to work on other things
            currentDispatcher.Invoke(delegate { }, DispatcherPriority.SystemIdle);
        }
    }
}

Bu çözümün dezavantajlarını (varsa) görmek için alana yeterince girmiyorum

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.