如何:侦听具有等待句柄的取消请求
如果方法在等待事件收到信号时受阻止,既无法检查取消令牌的值,也无法及时响应。 第一个示例展示了如何在使用 System.Threading.ManualResetEvent 等不本机支持统一取消框架的事件时解决此问题。 第二个示例展示了一种更简化的方法,即使用确实支持统一取消的 System.Threading.ManualResetEventSlim。
注意
某些情况下,当启用“仅我的代码”后,Visual Studio 会在引发异常的行中断运行并显示一条错误消息,该消息显示“用户代码未处理异常”。该错误是良性错误。 可以按 F5 继续运行,并查看下面示例中所示的异常处理行为。 为了阻止 Visual Studio 在第一个错误出现时中断,只需依次转到“工具”、“选项”、“调试”、“常规” 下,取消选中“仅我的代码”复选框即可。
示例 1
下面的示例使用 ManualResetEvent 展示了如何取消阻止不支持统一取消的等待句柄。
using System;
using System.Threading;
using System.Threading.Tasks;
class CancelOldStyleEvents
{
// Old-style MRE that doesn't support unified cancellation.
static ManualResetEvent mre = new ManualResetEvent(false);
static void Main()
{
var cts = new CancellationTokenSource();
// Pass the same token source to the delegate and to the task instance.
Task.Run(() => DoWork(cts.Token), cts.Token);
Console.WriteLine("Press s to start/restart, p to pause, or c to cancel.");
Console.WriteLine("Or any other key to exit.");
// Old-style UI thread.
bool goAgain = true;
while (goAgain)
{
char ch = Console.ReadKey(true).KeyChar;
switch (ch)
{
case 'c':
cts.Cancel();
break;
case 'p':
mre.Reset();
break;
case 's':
mre.Set();
break;
default:
goAgain = false;
break;
}
Thread.Sleep(100);
}
cts.Dispose();
}
static void DoWork(CancellationToken token)
{
while (true)
{
// Wait on the event if it is not signaled.
int eventThatSignaledIndex =
WaitHandle.WaitAny(new WaitHandle[] { mre, token.WaitHandle },
new TimeSpan(0, 0, 20));
// Were we canceled while waiting?
if (eventThatSignaledIndex == 1)
{
Console.WriteLine("The wait operation was canceled.");
throw new OperationCanceledException(token);
}
// Were we canceled while running?
else if (token.IsCancellationRequested)
{
Console.WriteLine("I was canceled while running.");
token.ThrowIfCancellationRequested();
}
// Did we time out?
else if (eventThatSignaledIndex == WaitHandle.WaitTimeout)
{
Console.WriteLine("I timed out.");
break;
}
else
{
Console.Write("Working... ");
// Simulating work.
Thread.SpinWait(5000000);
}
}
}
}
Imports System.Threading
Imports System.Threading.Tasks
Class CancelOldStyleEvents
' Old-style MRE that doesn't support unified cancellation.
Shared mre As New ManualResetEvent(False)
Shared Sub Main9()
Dim cts As New CancellationTokenSource()
' Pass the same token source to the delegate and to the task instance.
Task.Run(Sub() DoWork(cts.Token), cts.Token)
Console.WriteLine("Press c to cancel, p to pause, or s to start/restart.")
Console.WriteLine("Or any other key to exit.")
' Old-style UI thread.
Dim goAgain As Boolean = True
While goAgain
Dim ch As Char = Console.ReadKey(True).KeyChar
Select Case ch
Case "c"c
cts.Cancel()
Case "p"c
mre.Reset()
Case "s"c
mre.Set()
Case Else
goAgain = False
End Select
Thread.Sleep(100)
End While
cts.Dispose()
End Sub
Shared Sub DoWork(ByVal token As CancellationToken)
While True
' Wait on the event if it is not signaled.
Dim waitHandles() As WaitHandle = {mre, token.WaitHandle}
Dim eventThatSignaledIndex =
WaitHandle.WaitAny(waitHandles, _
New TimeSpan(0, 0, 20))
' Were we canceled while waiting?
' The first If statement is equivalent to
' token.ThrowIfCancellationRequested()
If eventThatSignaledIndex = 1 Then
Console.WriteLine("The wait operation was canceled.")
Throw New OperationCanceledException(token)
' Were we canceled while running?
ElseIf token.IsCancellationRequested = True Then
Console.WriteLine("Cancelling per user request.")
token.ThrowIfCancellationRequested()
' Did we time out?
ElseIf eventThatSignaledIndex = WaitHandle.WaitTimeout Then
Console.WriteLine("The wait operation timed out.")
Exit While
Else
' Simulating work.
Console.Write("Working... ")
Thread.SpinWait(5000000)
End If
End While
End Sub
End Class
示例 2
下面的示例使用 ManualResetEventSlim 展示了如何取消阻止确实支持统一取消的协调基元。 相同的方法可以与其他轻型协调基元(如 SemaphoreSlim
和 CountdownEvent)结合使用。
using System;
using System.Threading;
using System.Threading.Tasks;
class CancelNewStyleEvents
{
// New-style MRESlim that supports unified cancellation
// in its Wait methods.
static ManualResetEventSlim mres = new ManualResetEventSlim(false);
static void Main()
{
var cts = new CancellationTokenSource();
// Pass the same token source to the delegate and to the task instance.
Task.Run(() => DoWork(cts.Token), cts.Token);
Console.WriteLine("Press c to cancel, p to pause, or s to start/restart,");
Console.WriteLine("or any other key to exit.");
// New-style UI thread.
bool goAgain = true;
while (goAgain)
{
char ch = Console.ReadKey(true).KeyChar;
switch (ch)
{
case 'c':
// Token can only be canceled once.
cts.Cancel();
break;
case 'p':
mres.Reset();
break;
case 's':
mres.Set();
break;
default:
goAgain = false;
break;
}
Thread.Sleep(100);
}
cts.Dispose();
}
static void DoWork(CancellationToken token)
{
while (true)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("Canceled while running.");
token.ThrowIfCancellationRequested();
}
// Wait on the event to be signaled
// or the token to be canceled,
// whichever comes first. The token
// will throw an exception if it is canceled
// while the thread is waiting on the event.
try
{
// mres is a ManualResetEventSlim
mres.Wait(token);
}
catch (OperationCanceledException)
{
// Throw immediately to be responsive. The
// alternative is to do one more item of work,
// and throw on next iteration, because
// IsCancellationRequested will be true.
Console.WriteLine("The wait operation was canceled.");
throw;
}
Console.Write("Working...");
// Simulating work.
Thread.SpinWait(500000);
}
}
}
Imports System.Threading
Imports System.Threading.Tasks
Class CancelNewStyleEvents
' New-style MRESlim that supports unified cancellation
' in its Wait methods.
Shared mres As ManualResetEventSlim = New ManualResetEventSlim(False)
Shared Sub Main10()
Dim cts As New CancellationTokenSource()
' Pass the same token source to the delegate and to the task instance.
Task.Run(Sub() DoWork(cts.Token), cts.Token)
Console.WriteLine("Press c to cancel, p to pause, or s to start/restart,")
Console.WriteLine("or any other key to exit.")
' New-style UI thread.
Dim goAgain As Boolean = True
While goAgain = True
Dim ch As Char = Console.ReadKey(True).KeyChar
Select Case ch
Case "c"c
' Token can only be canceled once.
cts.Cancel()
Case "p"c
mres.Reset()
Case "s"c
mres.Set()
Case Else
goAgain = False
End Select
Thread.Sleep(100)
End While
cts.Dispose()
End Sub
Shared Sub DoWork(ByVal token As CancellationToken)
While True
If token.IsCancellationRequested Then
Console.WriteLine("Canceled while running.")
token.ThrowIfCancellationRequested()
End If
' Wait on the event to be signaled
' or the token to be canceled,
' whichever comes first. The token
' will throw an exception if it is canceled
' while the thread is waiting on the event.
Try
' mres is a ManualResetEventSlim
mres.Wait(token)
Catch e As OperationCanceledException
' Throw immediately to be responsive. The
' alternative is to do one more item of work,
' and throw on next iteration, because
' IsCancellationRequested will be true.
Console.WriteLine("Canceled while waiting.")
Throw
End Try
' Simulating work.
Console.Write("Working...")
Thread.SpinWait(500000)
End While
End Sub
End Class