Tại sao không chờ đợi Task.Run () đồng bộ hóa trở lại bối cảnh UI Thread / origin?


8

Tôi nghĩ rằng tôi đã hiểu mô hình chờ đồng bộ và Task.Runhoạt động.
Nhưng tôi tự hỏi tại sao trong ví dụ mã sau đây awaitkhông đồng bộ trở lại luồng UI sau khi trở về từ tác vụ đã hoàn thành.

public async Task InitializeAsync()
{
    Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}"); // "Thread: 1"
    double value = await Task.Run(() =>
    {
        Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}"); // Thread: 6

        // Do some CPU expensive stuff
        double x = 42;
        for (int i = 0; i < 100000000; i++)
        {
            x += i - Math.PI;
        }
        return x;
    }).ConfigureAwait(true);
    Console.WriteLine($"Result: {value}");
    Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}"); // Thread: 6  - WHY??
}

Mã này chạy trong ứng dụng .NET Framework WPF trên hệ thống Windows 10 với Trình gỡ lỗi Visual Studio 2019 đính kèm.
Tôi đang gọi mã này từ nhà xây dựng của Applớp tôi .

public App()
{
    this.InitializeAsync().ConfigureAwait(true);
}

Có thể đó không phải là cách tốt nhất, nhưng tôi không chắc đây có phải là lý do cho hành vi kỳ lạ đó không.

Mã bắt đầu với luồng UI và sẽ thực hiện một số tác vụ. Với awaitthao tác và ConfigureAwait(true)sau khi Nhiệm vụ kết thúc, nó sẽ tiếp tục trên Chủ đề chính (1). Nhưng nó không.

Tại sao?


4
@SushantYelpale không chính xác
MickyD

Câu trả lời:


10

Đó là một điều khó khăn.

Bạn đang gọi awaittrên chủ đề UI, đó là sự thật. Nhưng! Bạn đang làm nó bên trong Appconstructor của.

Hãy nhớ rằng mã khởi động được tạo ngầm định trông như thế này:

public static void Main()
{
    var app = new YourNamespace.App();
    app.InitializeComponent();
    app.Run();
}

Vòng lặp sự kiện, được sử dụng để quay trở lại luồng chính, chỉ được bắt đầu như một phần của Runthực thi. Vì vậy, trong quá Apptrình xây dựng chạy, không có vòng lặp sự kiện. Chưa.

Kết quả là SynchronizationContext, về mặt kỹ thuật, chịu trách nhiệm trả lại luồng cho luồng chính sau await, là nulltại hàm tạo của Ứng dụng.

( SynchronizationContextđược ghi lại await trước khi chờ đợi, do đó, không có vấn đề gì sau khi hoàn thành Task, đã có giá trị SynchronizationContext: giá trị được bắt giữ null, vì vậy awaittiếp tục thực hiện trên chuỗi nhóm luồng.)

Vì vậy, vấn đề không phải là bạn đang chạy mã trong một hàm tạo, vấn đề là bạn đang chạy nó trong hàm tạo Appcủa nó, lúc đó ứng dụng chưa được thiết lập đầy đủ để thực thi. Mã tương tự trong hàm MainWindowtạo sẽ hoạt động tốt.

Hãy thực hiện một số thử nghiệm:

public App()
{
    Console.WriteLine($"sc = {SynchronizationContext.Current?.ToString() ?? "null"}");
}

protected override void OnStartup(StartupEventArgs e)
{
    Console.WriteLine($"sc = {SynchronizationContext.Current?.ToString() ?? "null"}");
    base.OnStartup(e);
}

Đầu ra đầu tiên cho

sc = null

thư hai

sc = System.Windows.Threading.DispatcherSynchronizationContext

Vì vậy, bạn có thể thấy rằng đã OnStartupcó một bối cảnh đồng bộ hóa. Vì vậy, nếu bạn chuyển InitializeAsync()đến OnStartup, nó sẽ hoạt động như bạn mong đợi.

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.