The TPL is great but sometimes you have to work within a framework which schedules background tasks for you
without giving you access to a Task
of some kind.
I had a situation like the following
1
2
3
4
5
6
7
8
| private void DoSomeWork(CancellationToken token)
{
token.ThrowIfCancellationRequested();
// do lots of work
}
// some other code that might happen in a UI thread
AsyncWorkScheduler.Schedule(()=> DoSomeWork(token));
|
I didn’t really have access to how the background task was scheduled, and to be honest I didn’t really care.
What I needed was a way to wait for the work to finish, whether it finished by itself or if it was
cancelled via the CancellationToken
.
I wanted to use await
to avoid blocking any UI threads as well.
I could use the AutoResetEvent
as a way to perform cross-thread signaling to indicate the work is done.
Simple enough
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| private readonly AutoResetEvent _autoReset = new AutoResetEvent(false);
private void DoSomeWork(CancellationToken token)
{
var cancellation = (CancellationToken)obj;
try
{
token.ThrowIfCancellationRequested();
// do lots of work
}
finally
{
// yay! All done
_autoReset.Set();
}
}
|
So how would you use await with an AutoResetEvent
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| /// <summary>
/// Create a task based on the wait handle, which is the base class
/// for quite a few synchronization primitives
/// </summary>
/// <param name="handle">The wait handle</param>
/// <returns>A valid task</returns>
private static Task WaitHandleAsTask(WaitHandle handle)
{
return WaitHandleAsTask(handle, Timeout.InfiniteTimeSpan);
}
/// <summary>
/// Create a task based on the wait handle, which is the base class
/// for quite a few synchronization primitives
/// </summary>
/// <param name="handle">The wait handle</param>
/// <param name="timeout">The timeout</param>
/// <returns>A valid task</returns>
private static Task WaitHandleAsTask(WaitHandle handle, TimeSpan timeout)
{
var tcs = new TaskCompletionSource<object>();
var registration = ThreadPool.RegisterWaitForSingleObject(handle, (state, timedOut) =>
{
var localTcs = (TaskCompletionSource<object>)state;
if (timedOut)
{
localTcs.SetCanceled();
}
else
{
localTcs.SetResult(null);
}
}, tcs, timeout, true);
// clean up the RegisterWaitHandle
tcs.Task.ContinueWith((_, state) => ((RegisteredWaitHandle)state).Unregister(null), registration, TaskScheduler.Default);
return tcs.Task;
}
|
ThreadPool.RegisterWaitForSingleObject
performs a wait in a thread pool thread, and when the handle is signal, the delegate executes
executes in a worker thread. This will signal the task completion.
So your wait operation ends up looking like
1
2
3
4
5
| void async SomeAsyncMethod(){
// .. stuff and work
await WaitHandleAsTask(_autoReset);
// .. more stuff and work
}
|
Pretty neat eh?