Tôi có một tình huống mà một cuộc gọi đến CancellationTokenSource.Cancel
không bao giờ trở lại. Thay vào đó, sau khi Cancel
được gọi (và trước khi nó trả về), việc thực hiện tiếp tục với mã hủy của mã đang bị hủy. Nếu mã được hủy bỏ không sau đó gọi bất kỳ mã awaitable sau đó người gọi ban đầu được gọi là Cancel
không bao giờ được kiểm soát trở lại. Điều này rất lạ. Tôi mong đợi Cancel
để chỉ cần ghi lại yêu cầu hủy và trả lại ngay lập tức độc lập với chính việc hủy. Thực tế là các thread mà Cancel
đang được gọi là kết thúc lên thực thi mã thuộc về các hoạt động đang được hủy bỏ và nó như vậy trước khi trở về người gọi Cancel
trông giống như một lỗi trong khuôn khổ.Một cuộc gọi đến CancellationTokenSource.Cancel không bao giờ trả lại
Sau đây là cách này đi:
Có một đoạn mã, chúng ta hãy gọi nó là “mã nhân viên” được chờ đợi vào một số mã async. Để làm cho mọi việc đơn giản giả sử mã này đang chờ trên Task.Delay:
try { await Task.Delay(5000, cancellationToken); // … } catch (OperationCanceledException) { // …. }
Ngay trước khi “mã nhân viên” gọi Task.Delay
nó được thực hiện trên thread T1. Việc tiếp tục (đó là dòng sau "chờ đợi" hoặc khối bên trong bắt) sẽ được thực hiện sau đó trên T1 hoặc có thể trên một số chủ đề khác tùy thuộc vào một loạt các yếu tố.
- Có một đoạn mã khác, hãy gọi nó là “mã khách hàng” quyết định hủy
Task.Delay
. Mã này gọicancellationToken.Cancel
. Cuộc gọi đếnCancel
được thực hiện trên luồng T2.
Tôi mong đợi chuỗi T2 sẽ tiếp tục bằng cách quay lại người gọi Cancel
. Tôi cũng mong đợi để xem nội dung của catch (OperationCanceledException)
thực hiện rất sớm trên thread T1 hoặc trên một số chủ đề khác hơn T2.
Điều gì xảy ra tiếp theo là đáng ngạc nhiên. Tôi thấy rằng trên thread T2, sau khi Cancel
được gọi, việc thực hiện tiếp tục ngay lập tức với khối bên trong catch (OperationCanceledException)
. Và điều đó xảy ra trong khi Cancel
vẫn còn trên callstack. Nó giống như cuộc gọi đến Cancel
bị tấn công bởi mã mà nó đang bị hủy. Dưới đây là một ảnh chụp màn hình của Visual Studio cho thấy cuộc gọi này stack:
Nhiều bối cảnh
Dưới đây là một số bối cảnh hơn về những gì các mã thực tế thực hiện: Có một “mã nhân” mà tích lũy yêu cầu. Yêu cầu đang được gửi bởi một số "mã khách hàng". Mỗi vài giây "mã công nhân" xử lý các yêu cầu này. Các yêu cầu được xử lý được loại bỏ khỏi hàng đợi. Tuy nhiên, thỉnh thoảng, “mã khách hàng” quyết định rằng nó đến một điểm mà nó muốn yêu cầu được xử lý ngay lập tức. Để truyền đạt điều này đến “mã công nhân”, nó gọi phương thức Jolt
rằng “mã công nhân” cung cấp. Phương thức Jolt
đang được gọi bằng "mã máy khách" thực hiện tính năng này bằng cách hủy bỏ một Task.Delay
được thực hiện bởi vòng lặp chính của công nhân. Mã của nhân viên bị hủy Task.Delay
và tiếp tục xử lý các yêu cầu đã được xếp hàng đợi.
Mã thực tế bị tước xuống biểu mẫu đơn giản nhất và mã là available on GitHub.
Môi trường
Vấn đề này có thể được sao chép trong giao diện điều khiển ứng dụng, đại lý nền cho Universal Apps dành cho Windows và các đại lý nền cho Universal Apps cho Windows Phone 8.1.
Sự cố không thể được sao chép trong ứng dụng Universal cho Windows nơi mã hoạt động như tôi mong đợi và cuộc gọi đến Cancel
sẽ trả về ngay lập tức.
* Vấn đề không thể được sao chép trong ứng dụng Universal * - bởi vì trong trường hợp này, có một bối cảnh đồng bộ hóa trên chuỗi nơi bạn gọi 'await Task.Delay (...)', do đó việc tiếp tục được kích hoạt bởi 'CancellationTokenSource.Cancel' được đăng không đồng bộ vào ngữ cảnh đó. Do đó, không có bế tắc. – Noseratio