I have a worker thread that reports progress by raising an event.
private volatile bool canRun;
public void Run()
{
for (int i = 0; i < count && canRun; i++)
{
// do stuff
OnProgressMade((float)i / (float)count);
}
OnWorkFinished();
}
public void Cancel()
{
canRun = false;
// wait for the thread to finish
}
In the Cancel
method, I would like to wait for the thread to finish. Otherwise, someone might call Cancel
and then immediately Dispose
while the thread is still "doing stuff". If I used Join
, there might be a deadlock - UI waits for the thread to cancel, thread waits for the UI to handle the ProgressMade
event.
Is there a way to solve this nicely, or is it bad design from the beginning? My current solution relies on the UI waiting for the WorkFinished
event before disposing the worker.
In the comments you hint that you might be able to use await (which is possible even on .NET 4.0). It could look like this:
MyWorker w;
CancellationTokenSource cts;
void OnStart(eventargs bla bla) {
cts = new ...();
w = new ...(cts.Token);
}
void OnCancel(eventargs bla bla) {
cts.Cancel();
await w.WaitForShutdown();
MsgBox("cancelled");
}
And we need to make MyWorker cooperate:
class MyWorker {
CancellationToken ct = <from ctor>;
TaskCompletionSource<object> shutdownCompletedTcs = new ...();
public void Run()
{
for (int i = 0; i < count && !ct.IsCancellationRequested; i++)
{
// do stuff
OnProgressMade((float)i / (float)count);
}
//OnWorkFinished(); //old
shutdownCompletedTcs.SetResult(null); //new, set "event"
}
public Task WaitForShutdown() {
return shutdownCompletedTcs.Task;
}
}
(Quickly slapped together.) Note, that all wait operations use await. They release the UI thread without interrupting your control flow.
Also note, that MyWorker
is cooperatively cancellable. It does not know anything about the UI.