2016-09-02 20 views
7

Ban đầu tôi nghĩ rằng tất cả các lần tiếp tục được thực thi trên luồng (được đưa ra một bối cảnh đồng bộ hóa mặc định). Tuy nhiên điều này dường như không xảy ra khi tôi sử dụng TaskCompletionSource.Trong trường hợp nào TaskCompletionSource.SetResult() chạy liên tục đồng bộ?

Mã của tôi trông giống như sau:

Task<int> Foo() { 
    _tcs = new TaskCompletionSource<int>(); 
    return _tcs.Task; 
} 

async void Bar() { 
    Console.WriteLine(Thread.Current.ManagedThreadId); 
    Console.WriteLine($"{Thread.Current.ManagedThreadId} - {await Foo()}"); 
} 

Bar được gọi bằng một sợi chỉ cụ thể và TaskCompletionSource vẫn unset trong một thời gian, có nghĩa là nhiệm vụ trở IsComplete = false. Sau đó, sau một thời gian, cùng một luồng sẽ tiến hành gọi _tcs.SetResult(x), theo sự hiểu biết của tôi nên chạy phần tiếp tục trên luồng. Tuy nhiên, những gì tôi quan sát thấy trong ứng dụng của tôi là luồng chạy tiếp tục trên thực tế vẫn là cùng một luồng, như thể việc tiếp tục được gọi đồng bộ ngay khi SetResult được gọi.

Tôi thậm chí đã thử thiết lập một điểm ngắt trên SetResult và bước qua nó (và có điểm ngắt trong phần tiếp theo), mà đến lượt nó thực sự tiếp tục gọi sự tiếp tục đồng bộ.

Khi nào chính xác thì SetResult() ngay lập tức gọi tiếp tục đồng bộ?

+0

Sẽ giúp ích nếu bạn cung cấp [mcve] thay vì * nửa * của nó, vì vậy chúng tôi có thể thử nghiệm với chính nó ... –

Trả lời

4

SetResultthường là chạy liên tục từ TCS đồng bộ. Có ngoại lệ chính cho điều này là nếu bạn rõ ràng chuyển vào cờ TaskContinuationOptions.RunContinuationsAsynchronously khi tạo TCS (mới trong .NET 4.6). Kịch bản khác khi nó chạy mọi thứ không đồng bộ là nếu nó cho rằng luồng hiện tại bị tiêu diệt. Điều này là khá quan trọng, bởi vì nếu bạn không cẩn thận, bạn có thể kết thúc có mã gọi kiểm soát của một chủ đề mà có nghĩa là để được làm công việc khác (như: giao dịch với socket IO).

+0

BTW: Điều gì xảy ra khi bạn gọi 'SetResult()' trên một chuỗi không phải là người gọi chờ đợi ban đầu? Không nên tiếp tục cố gắng để có được chủ đề người gọi ban đầu vì mặc định 'ConfigureAwait (...)'? – Petrroll

+0

@Petrroll nếu không có 'SyncronisationContext.Current' khi chức năng được gọi là nó sẽ không có chuỗi gốc để truy cập. –

+0

Và nếu có một số SyncContext rõ ràng? – Petrroll

5

Ban đầu, tôi nghĩ rằng tất cả các lần tiếp tục được thực hiện trên luồng (được cung cấp ngữ cảnh đồng bộ mặc định). Tuy nhiên điều này dường như không xảy ra khi tôi sử dụng một TaskCompletionSource.

Thực tế, khi sử dụng await, hầu hết các lần tiếp tục được thực hiện đồng bộ.

Câu trả lời của Marc rất tuyệt vời; Tôi chỉ muốn đi vào chi tiết hơn một chút ...

TaskCompletionSource<T> theo mặc định sẽ hoạt động đồng bộ khi Set* được gọi. Set* sẽ hoàn thành nhiệm vụ phát hành các lần tiếp tục trong một cuộc gọi phương thức duy nhất. (Điều này có nghĩa là gọi số Set* trong khi đang giữ khóa là công thức cho các khóa chết.)

Tôi sử dụng cụm từ kỳ lạ "cấp tiếp tục" ở đó bởi vì nó có thể hoặc không thực sự thực thi chúng; thêm về điều đó sau.

Cờ TaskCreationOptions.RunContinuationsAsynchronously sẽ thông báo TaskCompletionSource<T> để phát hành liên tục không đồng bộ. Điều này phá vỡ hoàn thành nhiệm vụ (mà vẫn được thực hiện ngay lập tức bởi Set*) từ việc phát hành các lần tiếp tục (chỉ được kích hoạt bởi cuộc gọi Set*).Vì vậy, với RunContinuationsAsynchronously, cuộc gọi Set* sẽ chỉ hoàn thành nhiệm vụ; nó sẽ không thực thi đồng bộ liên tục. (Điều này có nghĩa là gọi số Set* trong khi giữ khóa an toàn.)

Nhưng quay lại trường hợp mặc định, đồng thời phát hành đồng bộ liên tục.

Mỗi lần tiếp tục cũng có cờ; theo mặc định, việc tiếp tục được thực thi không đồng bộ nhưng có thể được thực hiện đồng bộ theo TaskContinuationOptions.ExecuteSynchronously. (Lưu ý rằng awaitdoes use this flag - liên kết đến blog của tôi; về mặt kỹ thuật đây là chi tiết triển khai và không được chính thức tài liệu hóa).

Tuy nhiên, ngay cả khi ExecuteSynchronously được chỉ định, có a number of situations where the continuation is not executed synchronously:

  • Nếu có một TaskScheduler gắn liền với việc tiếp tục, lịch trình được cung cấp tùy chọn không chấp nhận xử lí hiện tại, trong trường hợp mà nhiệm vụ là xếp hàng vào số TaskScheduler thay vì thực hiện đồng bộ.
  • Nếu chuỗi hiện tại đang bị hủy, thì nhiệm vụ sẽ được xếp hàng ở nơi khác.
  • Nếu ngăn xếp của luồng hiện tại quá sâu thì nhiệm vụ được xếp hàng ở nơi khác. (Đây chỉ là một heuristic, và không được bảo đảm để tránh StackOverflowException).

Đó là một vài điều kiện, nhưng với thử nghiệm ứng dụng điều khiển đơn giản của bạn, tất cả họ đang đáp ứng:

  • TaskCompletionSource<T> không xác định RunContinuationsAsynchronously.
  • Việc tiếp tục (await) không chỉ định ExecuteSynchronously.
  • Việc tiếp tục không được chỉ định TaskScheduler.
  • Chuỗi mục tiêu có thể thực hiện việc tiếp tục (không bị hủy bỏ; ngăn xếp là ok).

Theo nguyên tắc chung, tôi sẽ nói mọi cách sử dụng TaskCompletionSource<T> phải chỉ định TaskCreationOptions.RunContinuationsAsynchronously. Cá nhân tôi nghĩ rằng ngữ nghĩa phù hợp hơn và ít gây ngạc nhiên hơn với lá cờ đó.

Các vấn đề liên quan