So I have been playing around with threads for the last couple months and while my output is as expected i have a feeling I'm not doing this the best way. I can't seem to get a straight answer from anyone i work with on what is best practice so i thought i would ask you guys.
Question: I'm going to try to make this simple so bear with me. Say i ha开发者_如何学Pythonve a form that has a start and stop button. The start button fires and event that starts a thread. Inside this thread's DoWork
it is going to call 3 methods. Method1() prints to the console "A\n" 10 times with a pause of 10 seconds in between. Method2() and Method3() are the exact same just different letter and different pause times in between Console.WriteLine
. Now when you press the stop button you want the response to be immediate. I don't want to have to wait for the methods to complete. How do i go about this?
The way i have been doing this is passing my BackgroundWorker to each method and checking the worker.CancellationPending like so
public void Method1(BackgroundWorker worker)
{
for(int i = 0; i < 10 && !worker.CancellationPending; ++i)
{
Console.WriteLine("A");
for(int j = 0; j < 100 && !worker.CancellationPending; ++i)
{
Thread.Sleep(100);
}
}
}
Like i said this give me the desired result however imagine that method1 becomes a lot more complex, let say it is using a DLL to write that has a keydown and a key up. If i just abort the thread i could possibly leave myself in an undesired state as well. I find myself littering my code with !worker.CancellationPending
. Practically every code block i am checking CancellationPending. I look at a lot of example on line and i rarely see people passing a thread around like i am. What is best practices on this?
Consider using iterators (yield return) to break up the steps.
public void Method1(Backgroundworker worker)
{
foreach (var discard in Method1Steps)
{
if (worker.CancelationPending)
return;
}
}
private IEnumerable<object> Method1Steps()
{
for (int i = 0; i < 10; ++i)
{
yield return null;
Console.WriteLine("A");
for (int j = 0; j < 100; ++i)
{
Thread.Sleep(100);
yield return null;
}
}
}
This solution may be harder to implement if you have a bunch of try/catch/finally or a bunch of method calls that also need to know about cancelation.
Yes, you are doing it correctly. It may seem awkward at first, but it really is the best option. It is definitely far better than aborting a thread. Loop iterations, as you have discovered, are ideal candidates for checking CancelationPending
. This is because a loop iteration often isolates a logical unit of work and thus easily delineate a safe point. Safe points are markers in the execution of a thread where termination can be easily accomplished without corrupting any data.
The trick is to poll CancelationPending
at safe points frequently enough to provide timely feedback to the caller that cancelation completed successfully, but not too frequently to negatively effect performance or as to "litter the code".
In your specific case the inner loop is the best place to poll CancelationPending
. I would omit the check on the outer loop. The reason is because the inner loop is where most of the time is spent. The check on the outer loop would be pointless because the outer loop does very little actual work except to get the inner loop going.
Now, on the GUI side you might want to grey out the stop button to let the user know that the cancelation request was accepted. You could display a message like "cancelation pending" or the like to make it clear. Once you get the feedback that cancelation is complete then you could remove the message.
Well, if you are in the situation where you have to abort a CPU-intensive thread, then you are somwhat stuck with testing an 'Abort' boolean, (or cancellation token), in one loop or another, (maybee not the innermost one - depends on how long this takes). AFAIK, you can just 'return' from the inner loop, so exiting the method - no need to check at every level! To minimize the overhead on this, try to make it a local-ish boolean, ie try not to dereference it through half-a-dozen ...... classes every time .
Maybee inherit classes from 'Stoppable', that has an 'Abort' method and a 'Stop' boolean? You example thread above is spending most time sleeping, so you get 50ms average latency before you get to check anything. In such a case, you could wait on some event with a timeout instead of sleeping. Override 'Abort' to set the event as well as calling the inherited Abort & so terminate the wait early. You could also set the event in the cancellationToken delegate/callback, should you implement this new functionality as described by Dan.
There are acually very few Windows API etc. that are not easily 'unstickable' or don't have asynchronous, 'Ex' versions, so it's err.. 'nearly' always possible to cancel, one way or another, eg. closing sockets to force a socket read to except, writing temporary file to force Folder Change Notifications to return.
Rgds, Martin
精彩评论