Thread.Abort and alternatives

2020-07-18 02:23发布

问题:

This is more out of personal curiosity/interest than a specific problem I'm trying to solve.

Suppose you have a program that is performing some operation on user-supplied information (such as a search string) that changes as the user types it. Suppose that you want to show the user the most relevant information for what they've typed at any given time.

If threads were really abortable, we could simply have one thread running based on the last-changed search string, and cancel any previous threads that were in progress.

Now the generally accepted best practice for doing this today is to use a delay timer as the user types, which waits for .5 to 1 second before initiating the operation. I hope it's obvious enough that this isn't an ideal solution, theoretically speaking (any kind of artificial delay imposes an artificial bottleneck that can never be overcome, even if it is only 0.5 seconds).

Furthermore, today's best practice goes on to state that any subsequent operations should wait for the previous one to complete before executing. In a world where we can't abort operations, this makes sense, but again, theoretically speaking this is far from ideal. Imagine the user types a single character and pauses just long enough for the operation to begin. Suppose that this operation takes 10 seconds to execute. The user is now forced to wait an unacceptable amount of time before the results of his/her query are visible.

An (unideal) workaround to this would be to have multiple operations executing concurrently, presuming it is safe to do so, but this would still result in significantly reduced performance.

So I'm just wondering people's thoughts on this, specific to .NET at least, and whether there are any new developments in this area since I last researched it that I should know about (parallel libraries perhaps?). I'd also be curious to know if any other languages/frameworks can handle this sort of fine-grained control of operations better than .NET can.

Cheers.

回答1:

With .Net 4's enhancements for Parallel Programming like the TPL and cancellation as mentioned by Mark Byers. The "Never abort a thread ! Unless you know exactly what you are doing...Even so don't." rule has thankfully been relaxed.

Another tool that allows a more "compositional" approach is Reactive Extensions for .NET (Rx). From the project's home page..

Rx is a library for composing asynchronous and event-based programs using observable collections.

They recently released a Hands-on-Lab....

Curing the asynchronous blues with the Reactive Extensions for .NET

...that includes a solution that, in a odd coincidence of timing, addresses your example... "Suppose that you want to show the user the most relevant information for what they've typed at any given time."..and the need cancel/abort/stop a task in progress.

In brief from the Lab...

In our running sample, we’ve building up a simple dictionary suggest application. Upon the user entering a search term, the application will fire off a call to a web service to get word suggestions back. Since we don’t want to block the UI, we’ll want to keep the communication with the dictionary service asynchronous too.

Following an excellent step-by-step the resulting solution is...

var txt = new TextBox(); 
var lst = new ListBox { Top = txt.Height + 10 }; 
var frm = new Form { 
    Controls = { txt, lst } 
}; 

// Turn the user input into a tamed sequence of strings. 
var textChanged = from evt in Observable
                  .FromEvent<EventArgs>(txt, "TextChanged") 
                  select ((TextBox)evt.Sender).Text; 

var input = textChanged 
            .Throttle(TimeSpan.FromSeconds(1)) 
            .DistinctUntilChanged(); 

// Bridge with the web service's MatchInDict method. 
var svc = new DictServiceSoapClient("DictServiceSoap"); 
var matchInDict = Observable
                  .FromAsyncPattern<string, string, string, DictionaryWord[]> 
                  (svc.BeginMatchInDict, svc.EndMatchInDict); 

Func<string, IObservable<DictionaryWord[]>> matchInWordNetByPrefix = 
    term => matchInDict("wn", term, "prefix"); 

// The grand composition connecting the user input with the web service. 
var res = from term in input 
          from word in matchInWordNetByPrefix(term).TakeUntil(input) 
          select word; 

// Synchronize with the UI thread and populate the ListBox or signal an error. 
using (res.ObserveOn(lst).Subscribe( 
    words => { 
        lst.Items.Clear(); 
        lst.Items.AddRange((from word in words select word.Word).ToArray()); 
    }, 
    ex => { 
        MessageBox.Show("An error occurred: " + ex.Message, frm.Text, 
                        MessageBoxButtons.OK, MessageBoxIcon.Error); 
    })) 
{ 
    Application.Run(frm); 
} // Proper disposal happens upon exiting the application. 

Enjoy.



回答2:

You should have a look at the Task Parallel Library in .NET 4 and the new cancellation model. Tasks can be run concurrently and it is safe to cancel them. It looks like a good fit for what you need.



回答3:

Basically, the clean way to abort a task is to ask it to nicely, and then let it shut itself down gracefully. That could be with a flag, a cancellation token (e.g. Parallel Extensions) or anything like that.

This only works when you're in control of the task - or when it's already programmed to do this - but it's useful in a large number of situations.

I don't agree with the "wait for the previous operation to complete before executing the next one" in general. It depends on the operation, of course - but if you can run two operations in parallel, can prevent the completion of the first one from messing things up, and don't mind the first one continuing to run until it notices the "stop please" flag you've just set as per the first paragraph of this answer, that's fine. It depends heavily on the situation though.



回答4:

Just because you shouldn't abuse Thread.Abort() doesn't mean you can't cancel an operation in a background thread.

// set to true to cancel
volatile bool cancel;

// background thread function
void foo()
{
    bool done = false;

    while (!done && !cancel)
    {
        ...
    }
}

The trick is to let your background thread exit cleanly, not unexpectedly.