Đồng bộ chờ hoạt động không đồng bộ và tại sao Wait () đóng băng chương trình tại đây


318

Lời nói đầu : Tôi đang tìm kiếm một lời giải thích, không chỉ là một giải pháp. Tôi đã biết giải pháp.

Mặc dù đã dành nhiều ngày để nghiên cứu các bài viết MSDN về Mẫu không đồng bộ dựa trên tác vụ (TAP), không đồng bộ và chờ đợi, tôi vẫn hơi bối rối về một số chi tiết tốt hơn.

Tôi đang viết một trình ghi nhật ký cho Ứng dụng Windows Store và tôi muốn hỗ trợ cả ghi nhật ký không đồng bộ và đồng bộ. Các phương thức không đồng bộ tuân theo TAP, các phương thức đồng bộ sẽ ẩn tất cả điều này, và trông và hoạt động như các phương thức thông thường.

Đây là phương pháp cốt lõi của ghi nhật ký không đồng bộ:

private async Task WriteToLogAsync(string text)
{
    StorageFolder folder = ApplicationData.Current.LocalFolder;
    StorageFile file = await folder.CreateFileAsync("log.log",
        CreationCollisionOption.OpenIfExists);
    await FileIO.AppendTextAsync(file, text,
        Windows.Storage.Streams.UnicodeEncoding.Utf8);
}

Bây giờ phương thức đồng bộ tương ứng ...

Phiên bản 1 :

private void WriteToLog(string text)
{
    Task task = WriteToLogAsync(text);
    task.Wait();
}

Điều này có vẻ đúng, nhưng nó không hoạt động. Toàn bộ chương trình đóng băng mãi mãi.

Phiên bản 2 :

Hmm .. Có lẽ nhiệm vụ chưa được bắt đầu?

private void WriteToLog(string text)
{
    Task task = WriteToLogAsync(text);
    task.Start();
    task.Wait();
}

Cái này ném InvalidOperationException: Start may not be called on a promise-style task.

Phiên bản 3:

Hmm .. Task.RunSynchronouslyâm thanh đầy hứa hẹn.

private void WriteToLog(string text)
{
    Task task = WriteToLogAsync(text);
    task.RunSynchronously();
}

Cái này ném InvalidOperationException: RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.

Phiên bản 4 (giải pháp):

private void WriteToLog(string text)
{
    var task = Task.Run(async () => { await WriteToLogAsync(text); });
    task.Wait();
}

Những công việc này. Vì vậy, 2 và 3 là các công cụ sai. Nhưng 1? Có gì sai với 1 và sự khác biệt của 4 là gì? Điều gì làm cho 1 gây ra đóng băng? Có một số vấn đề với các đối tượng nhiệm vụ? Có một bế tắc không rõ ràng?


Bất kỳ may mắn nhận được một lời giải thích ở nơi khác? Các câu trả lời dưới đây thực sự không cung cấp cái nhìn sâu sắc. Tôi thực sự đang sử dụng .net 4.0 chứ không phải 4.5 / 5 vì vậy tôi không thể sử dụng một số thao tác nhưng gặp vấn đề tương tự.
amadib

3
@amadib, ver.1 và 4 đã được giải thích trong [câu trả lời được trích dẫn. Ver.2 anв 3 cố gắng bắt đầu lại nhiệm vụ đã bắt đầu. Gửi câu hỏi của bạn. Không rõ làm thế nào bạn có thể có .NET 4.5 không đồng bộ / đang chờ các sự cố trên .NET 4.0
Gennady Vanin Геннадий Внаин

1
Phiên bản 4 là tùy chọn tốt nhất cho Xamarin Forms. Chúng tôi đã thử các lựa chọn còn lại và không làm việc và gặp bế tắc trong mọi trường hợp
Ramakrishna

Cảm ơn! Phiên bản 4 làm việc cho tôi. Nhưng nó vẫn chạy không đồng bộ? Tôi giả sử như vậy bởi vì từ khóa async ở đó.
sshirley

Câu trả lời:


189

Các awaitbên trong phương pháp không đồng bộ của bạn đang cố gắng để trở về với thread UI.

Vì luồng UI đang bận chờ đợi toàn bộ tác vụ hoàn thành, bạn có một bế tắc.

Di chuyển cuộc gọi async để Task.Run()giải quyết vấn đề.
Vì cuộc gọi async hiện đang chạy trên một chuỗi nhóm luồng, nên nó không cố gắng quay lại luồng UI và mọi thứ đều hoạt động.

Ngoài ra, bạn có thể gọi StartAsTask().ConfigureAwait(false)trước khi chờ thao tác bên trong để làm cho nó quay trở lại nhóm luồng thay vì luồng UI, tránh hoàn toàn bế tắc.


9
+1. Đây là một lời giải thích nữa - Chờ đợi, và UI, và bế tắc! Ôi trời!
Alexei Levenkov

13
Đây ConfigureAwait(false)là giải pháp thích hợp trong trường hợp này. Vì nó không cần phải gọi lại trong ngữ cảnh đã chụp, nên không nên. Là một phương thức API, nó nên xử lý nó bên trong, thay vì buộc tất cả người gọi phải rời khỏi bối cảnh UI.
Phục vụ

@Servy Đang hỏi kể từ khi bạn đề cập đến ConfigureAwait. Tôi đang sử dụng .net3.5 và tôi phải xóa cấu hình đang chờ vì nó không có sẵn trong thư viện async mà tôi đang sử dụng. Làm thế nào để tôi tự viết hoặc có một cách khác để chờ cuộc gọi không đồng bộ của tôi. Vì phương pháp của tôi bị treo quá. Tôi không có Nhiệm vụ nhưng không có Nhiệm vụ.Run. Shoud này có thể là một câu hỏi trên chính nó.
flexxxit

@flexxxit: Bạn nên sử dụng Microsoft.Bcl.Async.
SLaks

48

Gọi asyncmã từ mã đồng bộ có thể khá khó khăn.

Tôi giải thích lý do đầy đủ cho sự bế tắc này trên blog của tôi . Nói tóm lại, có một "bối cảnh" được lưu theo mặc định ở đầu mỗi awaitvà được sử dụng để tiếp tục phương thức.

Vì vậy, nếu điều này được gọi trong bối cảnh UI, khi awaithoàn thành, asyncphương thức sẽ cố gắng nhập lại bối cảnh đó để tiếp tục thực thi. Thật không may, mã sử dụng Wait(hoặc Result) sẽ chặn một luồng trong ngữ cảnh đó, vì vậy asyncphương thức không thể hoàn thành.

Các hướng dẫn để tránh điều này là:

  1. Sử dụng ConfigureAwait(continueOnCapturedContext: false)càng nhiều càng tốt. Điều này cho phép các asyncphương thức của bạn tiếp tục thực thi mà không cần phải nhập lại ngữ cảnh.
  2. Sử dụng asynctất cả các cách. Sử dụng awaitthay vì Resulthoặc Wait.

Nếu phương thức của bạn không đồng bộ một cách tự nhiên, thì bạn (có lẽ) không nên để lộ trình bao bọc đồng bộ .


Tôi cần phải thực thi một tác vụ Async trong một lệnh bắt () không hỗ trợ asynctôi sẽ làm điều này như thế nào và ngăn chặn tình huống cháy và quên.
Zapnologica

1
@Zapnologica: awaitđược hỗ trợ trong catchcác khối kể từ VS2015. Nếu bạn đang sử dụng phiên bản cũ hơn, bạn có thể gán ngoại lệ cho biến cục bộ và thực hiện awaitsau khối bắt .
Stephen Cleary

5

Đây là những gì tôi đã làm

private void myEvent_Handler(object sender, SomeEvent e)
{
  // I dont know how many times this event will fire
  Task t = new Task(() =>
  {
    if (something == true) 
    {
        DoSomething(e);  
    }
  });
  t.RunSynchronously();
}

làm việc tuyệt vời và không chặn luồng UI


0

Với ngữ cảnh đồng bộ hóa tùy chỉnh nhỏ, chức năng đồng bộ hóa có thể chờ hoàn thành chức năng async mà không tạo bế tắc. Đây là một ví dụ nhỏ cho ứng dụng WinForms.

Imports System.Threading
Imports System.Runtime.CompilerServices

Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        SyncMethod()
    End Sub

    ' waiting inside Sync method for finishing async method
    Public Sub SyncMethod()
        Dim sc As New SC
        sc.WaitForTask(AsyncMethod())
        sc.Release()
    End Sub

    Public Async Function AsyncMethod() As Task(Of Boolean)
        Await Task.Delay(1000)
        Return True
    End Function

End Class

Public Class SC
    Inherits SynchronizationContext

    Dim OldContext As SynchronizationContext
    Dim ContextThread As Thread

    Sub New()
        OldContext = SynchronizationContext.Current
        ContextThread = Thread.CurrentThread
        SynchronizationContext.SetSynchronizationContext(Me)
    End Sub

    Dim DataAcquired As New Object
    Dim WorkWaitingCount As Long = 0
    Dim ExtProc As SendOrPostCallback
    Dim ExtProcArg As Object

    <MethodImpl(MethodImplOptions.Synchronized)>
    Public Overrides Sub Post(d As SendOrPostCallback, state As Object)
        Interlocked.Increment(WorkWaitingCount)
        Monitor.Enter(DataAcquired)
        ExtProc = d
        ExtProcArg = state
        AwakeThread()
        Monitor.Wait(DataAcquired)
        Monitor.Exit(DataAcquired)
    End Sub

    Dim ThreadSleep As Long = 0

    Private Sub AwakeThread()
        If Interlocked.Read(ThreadSleep) > 0 Then ContextThread.Resume()
    End Sub

    Public Sub WaitForTask(Tsk As Task)
        Dim aw = Tsk.GetAwaiter

        If aw.IsCompleted Then Exit Sub

        While Interlocked.Read(WorkWaitingCount) > 0 Or aw.IsCompleted = False
            If Interlocked.Read(WorkWaitingCount) = 0 Then
                Interlocked.Increment(ThreadSleep)
                ContextThread.Suspend()
                Interlocked.Decrement(ThreadSleep)
            Else
                Interlocked.Decrement(WorkWaitingCount)
                Monitor.Enter(DataAcquired)
                Dim Proc = ExtProc
                Dim ProcArg = ExtProcArg
                Monitor.Pulse(DataAcquired)
                Monitor.Exit(DataAcquired)
                Proc(ProcArg)
            End If
        End While

    End Sub

     Public Sub Release()
         SynchronizationContext.SetSynchronizationContext(OldContext)
     End Sub

End Class
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.