Hỗ trợ async C # 5 sẽ giúp các vấn đề đồng bộ hóa giao diện người dùng như thế nào?


16

Tôi đã nghe ở đâu đó rằng C # 5 async-await sẽ tuyệt vời đến mức bạn sẽ không phải lo lắng về việc này:

if (InvokeRequired)
{
    BeginInvoke(...);
    return;
}
// do your stuff here

Có vẻ như cuộc gọi lại của một hoạt động đang chờ sẽ xảy ra trong luồng gốc của người gọi. Eric Lippert và Anders Hejlsberg đã tuyên bố nhiều lần rằng tính năng này bắt nguồn từ nhu cầu làm cho UI (đặc biệt là UI thiết bị cảm ứng) phản ứng nhanh hơn.

Tôi nghĩ rằng việc sử dụng phổ biến các tính năng như vậy sẽ là một cái gì đó như thế này:

public class Form1 : Form
{
    // ...
    async void GetFurtherInfo()
    {
        var temperature = await GetCurrentTemperatureAsync();
        label1.Text = temperature;
    }
}

Nếu chỉ sử dụng một cuộc gọi lại thì việc đặt văn bản nhãn sẽ đưa ra một ngoại lệ vì nó không được thực thi trong luồng của giao diện người dùng.

Cho đến nay tôi không thể tìm thấy bất kỳ tài nguyên xác nhận đây là trường hợp. Có ai biết về việc này không? Có tài liệu nào giải thích về mặt kỹ thuật việc này sẽ hoạt động như thế nào không?

Vui lòng cung cấp một liên kết từ một nguồn đáng tin cậy, đừng chỉ trả lời "có".


Điều này dường như rất khó xảy ra, ít nhất là trong chừng mực mà awaitchức năng có liên quan. Nó chỉ là rất nhiều đường cú pháp để tiếp tục đi qua . Có thể có một số cải tiến không liên quan đến WinForms được cho là có ích? Tuy nhiên, điều đó sẽ thuộc khuôn khổ .NET và không phải là C #.
Aaronaught

@Aaronaught Tôi đồng ý, đây là lý do tại sao tôi đặt câu hỏi chính xác. Tôi đã chỉnh sửa câu hỏi để làm rõ nơi tôi đến. Nghe có vẻ lạ khi họ sẽ tạo ra tính năng này và vẫn yêu cầu chúng tôi sử dụng kiểu mã InvokeRequired khét tiếng.
Alex

Câu trả lời:


17

Tôi nghĩ rằng bạn đang nhận được một vài điều nhầm lẫn ở đây. Những gì bạn đang yêu cầu đã có thể sử dụng System.Threading.Tasks, asyncawaittrong C # 5 sẽ cung cấp một chút đường cú pháp đẹp hơn cho cùng một tính năng.

Hãy sử dụng ví dụ Winforms - thả nút và hộp văn bản vào biểu mẫu và sử dụng mã này:

private void button1_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew<int>(() => DelayedAdd(5, 10))
        .ContinueWith(t => DelayedAdd(t.Result, 20))
        .ContinueWith(t => DelayedAdd(t.Result, 30))
        .ContinueWith(t => DelayedAdd(t.Result, 50))
        .ContinueWith(t => textBox1.Text = t.Result.ToString(),
            TaskScheduler.FromCurrentSynchronizationContext());
}

private int DelayedAdd(int a, int b)
{
    Thread.Sleep(500);
    return a + b;
}

Chạy nó và bạn sẽ thấy rằng (a) nó không chặn luồng UI và (b) bạn không gặp phải lỗi "hoạt động xuyên chuỗi không hợp lệ" thông thường - trừ khi bạn xóa TaskSchedulerđối số từ cuối cùng ContinueWith, trong trường hợp nào bạn sẽ

Đây là phong cách tiếp tục không có tiêu chuẩn . Phép thuật xảy ra trong TaskSchedulerlớp và đặc biệt là trường hợp được lấy bởi FromCurrentSynchronizationContext. Truyền phần này vào bất kỳ phần tiếp theo nào và bạn nói với nó rằng phần tiếp theo phải chạy trên bất kỳ luồng nào được gọi là FromCurrentSynchronizationContextphương thức - trong trường hợp này là luồng UI.

Người chờ đợi tinh vi hơn một chút theo nghĩa là họ nhận thức được họ đã bắt đầu từ chủ đề nào và chủ đề nào cần tiếp tục diễn ra. Vì vậy, đoạn mã trên có thể được viết tự nhiên hơn một chút :

private async void button1_Click(object sender, EventArgs e)
{
    int a = await DelayedAddAsync(5, 10);
    int b = await DelayedAddAsync(a, 20);
    int c = await DelayedAddAsync(b, 30);
    int d = await DelayedAddAsync(c, 50);
    textBox1.Text = d.ToString();
}

private async Task<int> DelayedAddAsync(int a, int b)
{
    Thread.Sleep(500);
    return a + b;
}

Hai nên nhìn rất giống nhau, và trong thực tế họ rất giống nhau. Các DelayedAddAsyncphương pháp hiện nay trả về một Task<int>thay vì một int, và do đó awaitchỉ là tát continuations vào mỗi một trong những. Sự khác biệt chính là nó đi dọc theo bối cảnh đồng bộ hóa trên mỗi dòng, vì vậy bạn không phải thực hiện nó một cách rõ ràng như trong ví dụ trước.

Về lý thuyết, sự khác biệt có ý nghĩa hơn rất nhiều. Trong ví dụ thứ hai, mọi dòng đơn trong button1_Clickphương thức thực sự được thực thi trong luồng UI, nhưng chính tác vụ ( DelayedAddAsync) chạy trong nền. Trong ví dụ đầu tiên, mọi thứ chạy trong nền , ngoại trừ việc gán textBox1.Textmà chúng ta đã gắn liền với bối cảnh đồng bộ hóa của giao diện người dùng.

Đó là điều thực sự thú vị await- thực tế là một người phục vụ có thể nhảy vào và ra khỏi cùng một phương thức mà không có bất kỳ cuộc gọi chặn nào. Bạn gọi await, luồng hiện tại quay lại xử lý tin nhắn và khi hoàn thành, người phục vụ sẽ chọn chính xác nơi nó rời đi, trong cùng một chuỗi mà nó bỏ qua. Nhưng về mặt Invoke/ BeginInvokeđộ tương phản của bạn trong câu hỏi, tôi ' Tôi rất tiếc phải nói rằng bạn nên ngừng làm điều đó từ lâu.


Điều đó rất thú vị @Aaronaught. Tôi đã nhận thức được phong cách tiếp tục nhưng không nhận thức được toàn bộ điều "bối cảnh đồng bộ hóa" này. Có tài liệu nào liên kết bối cảnh đồng bộ hóa này với C # 5 async-await không? Tôi hiểu đó là một tính năng hiện có nhưng thực tế họ đang sử dụng nó theo mặc định nghe có vẻ là một vấn đề lớn, đặc biệt bởi vì nó phải có một số tác động lớn trong hiệu suất, phải không? Bất kỳ ý kiến ​​thêm về điều đó? Cảm ơn câu trả lời của bạn bằng cách này.
Alex

1
@Alex: Để biết câu trả lời cho tất cả các câu hỏi tiếp theo, tôi khuyên bạn nên đọc Hiệu suất Async: Hiểu chi phí của Async và chờ đợi . Phần "Chăm sóc về bối cảnh" giải thích cách tất cả liên quan đến bối cảnh đồng bộ hóa.
Aaronaught

(Nhân tiện, bối cảnh đồng bộ hóa không phải là mới; chúng đã có trong khung từ năm 2.0. TPL chỉ giúp chúng dễ sử dụng hơn rất nhiều.)
Aaronaught

2
Tôi đang tự hỏi tại sao vẫn còn nhiều cuộc thảo luận về việc sử dụng phong cách InvokeRequired và hầu hết các chủ đề tôi đã thấy thậm chí không đề cập đến bối cảnh đồng bộ hóa. Nó sẽ giúp tôi tiết kiệm thời gian để đặt câu hỏi này lên ...
Alex

2
@Alex: Tôi đoán bạn không tìm đúng chỗ . Tôi không biết phải nói gì với bạn; có những phần lớn của cộng đồng .NET cần nhiều thời gian để bắt kịp. Chết tiệt, tôi vẫn thấy một số lập trình viên sử dụng ArrayListlớp trong mã mới. Bản thân tôi vẫn chưa có chút kinh nghiệm nào với RX. Mọi người tìm hiểu những gì họ cần biết và chia sẻ những gì họ đã biết, ngay cả khi những gì họ đã biết đã lỗi thời. Câu trả lời này có thể đã lỗi thời trong một vài năm.
Aaronaught

4

Có, đối với luồng UI, cuộc gọi lại của thao tác chờ sẽ xảy ra trên luồng gốc của người gọi.

Eric Lippert đã viết một loạt 8 phần về nó một năm trước: Cuộc phiêu lưu tuyệt vời trong mã hóa

EDIT: và đây là 'xây dựng / trình bày: kênh9'

BTW, bạn có nhận thấy rằng nếu bạn lật ngược "// build /", bạn sẽ nhận được "/ plinq //" ;-)


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.