Bạn có phải đặt Task.Run trong một phương thức để làm cho nó không đồng bộ không?


304

Tôi đang cố gắng để hiểu async đang chờ ở dạng đơn giản nhất. Tôi muốn tạo ra một phương thức rất đơn giản, thêm hai số vì lợi ích của ví dụ này, được cho phép, hoàn toàn không có thời gian xử lý, đó chỉ là vấn đề xây dựng một ví dụ ở đây.

ví dụ 1

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

Ví dụ 2

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

Nếu tôi chờ đợi DoWork1Async(), mã sẽ chạy đồng bộ hay không đồng bộ?

Tôi có cần bọc mã đồng bộ hóa Task.Runđể làm cho phương thức được chờ đợi và không đồng bộ để không chặn luồng UI không?

Tôi đang cố gắng tìm hiểu xem phương thức của tôi là một Taskhoặc trả về Task<T>tôi có cần phải bọc mã Task.Runđể làm cho nó không đồng bộ.

Câu hỏi ngu ngốc Tôi chắc chắn nhưng tôi thấy các ví dụ trên mạng nơi mọi người đang chờ mã không có gì không đồng bộ bên trong và không được bọc trong một Task.Runhoặc StartNew.


30
Không phải đoạn trích đầu tiên của bạn đưa ra bất kỳ cảnh báo nào?
Svick

Câu trả lời:


587

Trước tiên, hãy làm rõ một số thuật ngữ: "không đồng bộ" ( async) có nghĩa là nó có thể mang lại quyền kiểm soát cho luồng gọi trước khi bắt đầu. Trong một asyncphương thức, các điểm "năng suất" đó là các awaitbiểu thức.

Điều này rất khác so với thuật ngữ "không đồng bộ", vì (mis) được sử dụng bởi tài liệu MSDN trong nhiều năm có nghĩa là "thực thi trên một luồng nền".

Để hiểu thêm về vấn đề này, asyncrất khác so với "chờ đợi"; có một số asyncphương thức mà kiểu trả về không được chờ đợi và nhiều phương thức trả về kiểu không thể chờ được async.

Đủ về những gì họ không ; đây là những gì họ đang có :

  • Các asynctừ khóa cho phép một phương pháp không đồng bộ (có nghĩa là, nó cho phép awaitbiểu thức). asyncphương thức có thể trả về Task, Task<T>hoặc (nếu bạn phải) void.
  • Bất kỳ loại nào theo một mô hình nhất định có thể được chờ đợi. Các loại phổ biến nhất đang chờ đợi là TaskTask<T>.

Vì vậy, nếu chúng tôi định dạng lại câu hỏi của bạn thành "làm thế nào tôi có thể chạy một hoạt động trên một luồng nền theo cách mà nó được chờ đợi", câu trả lời là sử dụng Task.Run:

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

(Nhưng mô hình này là một cách tiếp cận kém; xem bên dưới).

Nhưng nếu câu hỏi của bạn là "làm cách nào để tạo một asyncphương thức có thể trả lại cho người gọi thay vì chặn", thì câu trả lời là khai báo phương thức asyncvà sử dụng awaitcho các điểm "mang lại" của nó:

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

Vì vậy, mô hình cơ bản của mọi thứ là có asyncmã phụ thuộc vào "awaitables" trong các awaitbiểu thức của nó . Những "awaitables" này có thể là các asyncphương thức khác hoặc chỉ là các phương thức thông thường trả về các awaitables. Phương pháp thông thường trở lại Task/ Task<T> có thể sử dụng Task.Runđể thực thi mã trên một sợi nền, hoặc (thường) họ có thể sử dụng TaskCompletionSource<T>hoặc một trong các phím tắt của nó ( TaskFactory.FromAsync, Task.FromResult, vv). Tôi không khuyên bạn nên gói toàn bộ một phương pháp vào Task.Run; phương pháp đồng bộ phải có chữ ký đồng bộ và nên để lại cho người tiêu dùng xem có nên gói trong 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());
  ...
}

Tôi có một async/ awaitgiới thiệu trên blog của tôi; cuối cùng là một số tài nguyên tiếp theo tốt. Các tài liệu MSDN asynccũng tốt một cách bất thường.


8
@sgnsajgon: Vâng. asyncphương pháp phải trả lại Task, Task<T>hoặc void. TaskTask<T>đang chờ đợi; voidkhông phải.
Stephen Cleary

3
Trên thực tế, một async voidchữ ký phương thức sẽ biên dịch, đó chỉ là một ý tưởng khá khủng khiếp khi bạn thả con trỏ vào tác vụ không đồng bộ của mình
IEatBagels

4
@TopinFrassi: Vâng, họ sẽ biên dịch, nhưng voidkhông được chờ đợi.
Stephen Cleary

4
@ohadinho: Không, những gì tôi đang nói trong bài đăng trên blog là khi toàn bộ phương pháp chỉ là một cuộc gọi đến Task.Run(như DoWorkAsynctrong câu trả lời này). Sử dụng Task.Runđể gọi một phương thức từ ngữ cảnh UI là thích hợp (như DoVariousThingsFromTheUIThreadAsync).
Stephen Cleary

2
Đúng chính xác. Việc sử dụng Task.Runđể gọi một phương thức là hợp lệ , nhưng nếu có Task.Runtất cả (hoặc gần như tất cả) mã của phương thức, thì đó là một kiểu chống - chỉ cần giữ phương thức đó đồng bộ và Task.Runtăng cấp độ.
Stephen Cleary

22

Một trong những điều quan trọng nhất cần nhớ khi trang trí một phương thức với async là ít nhất có một toán tử đang chờ bên trong phương thức. Trong ví dụ của bạn, tôi sẽ dịch nó như hiển thị bên dưới bằng cách sử dụng TaskCompletionSource .

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
Tại sao bạn sử dụng TaskCompletionSource, thay vì chỉ trả về tác vụ được trả về bởi phương thức Task.Run () (và thay đổi phần thân của nó để trả về kết quả)?
mỉa mai

4
Chỉ cần một lưu ý phụ. Một phương thức có chữ ký "async void" nói chung là thực tiễn xấu và được coi là mã xấu vì nó có thể dẫn đến bế tắc UI khá dễ dàng. Ngoại lệ chính là xử lý sự kiện không đồng bộ.
Jazzeroki

12

Khi bạn sử dụng Task.Run để chạy một phương thức, tác vụ sẽ lấy một luồng từ luồng để chạy phương thức đó. Vì vậy, từ quan điểm của luồng UI, nó "không đồng bộ" vì nó không chặn luồng UI. Điều này tốt cho ứng dụng máy tính để bàn vì bạn thường không cần nhiều luồng để xử lý các tương tác của người dùng.

Tuy nhiên, đối với ứng dụng web, mỗi yêu cầu được phục vụ bởi một luồng xử lý nhóm và do đó số lượng yêu cầu hoạt động có thể được tăng lên bằng cách lưu các luồng đó. Việc thường xuyên sử dụng các luồng xử lý luồng để mô phỏng hoạt động không đồng bộ là không thể mở rộng cho các ứng dụng web.

True Async không nhất thiết liên quan đến việc sử dụng một luồng cho các hoạt động I / O, chẳng hạn như truy cập tệp / DB, v.v. Bạn có thể đọc điều này để hiểu tại sao hoạt động I / O không cần các luồng. http://blog.stephencleary.com/2013/11/there-is-no-thread.html

Trong ví dụ đơn giản của bạn, đó là một phép tính thuần CPU, vì vậy sử dụng Task.Run là tốt.


Vì vậy, nếu tôi phải sử dụng một api bên ngoài đồng bộ trong bộ điều khiển api web, tôi KHÔNG nên kết thúc cuộc gọi đồng bộ trong Task.Run ()? Như bạn đã nói, làm như vậy sẽ giữ cho chuỗi yêu cầu ban đầu được bỏ chặn nhưng nó sử dụng một chuỗi nhóm khác để gọi api bên ngoài. Trong thực tế tôi nghĩ rằng đó vẫn là một ý tưởng tốt bởi vì theo cách này, theo lý thuyết, nó có thể sử dụng hai luồng chung để xử lý nhiều yêu cầu, ví dụ một luồng có thể xử lý nhiều yêu cầu đến và một luồng khác có thể gọi api bên ngoài cho tất cả các yêu cầu này không?
stt106

Tôi đồng ý. Tôi không nói rằng bạn hoàn toàn không nên thực hiện tất cả các cuộc gọi đồng bộ trong Task.Run (). Tôi chỉ đơn giản là chỉ ra vấn đề tiềm năng.
zeng yu

1
@ stt106 I should NOT wrap the synchronous call in Task.Run()đúng vậy. Nếu bạn làm như vậy, bạn sẽ chuyển đổi chủ đề. tức là bạn đang bỏ chặn luồng yêu cầu ban đầu nhưng bạn đang lấy một luồng khác từ luồng luồng có thể đã được sử dụng để xử lý yêu cầu khác. Kết quả duy nhất là chi phí chuyển đổi ngữ cảnh khi cuộc gọi được hoàn thành để đạt được mức tăng hoàn toàn bằng không
Saeb Amini
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.