TPL How to Perform a 'Call-Back'

2019-04-30 00:38发布

问题:

I have a small application that needs to test SQL connection strings for a number of connections (each done one-at-a-time). To do this I set the ConnectionTimeout = 5 temporarily to avoid a long wait if the connection is not valid and ConnectionTimeout = 0 (wait forever), say.

To avoid the UI hanging whilst we attempt to Open() a bad connection (even with ConnectionTimeout = 5 the wait for the SqlException can be up to twenty seconds), I want to run the test on a separate thread using Task Parallel Library (TPL). So I spin-off my new thread like:

Task<bool> asyncTestConn = Task.Factory.StartNew<bool>
    (() => TestConnection(conn, bShowErrMsg));
return asyncTestConn.Result;

The problem is that this is still locking the UI (clearly), as it is waiting for the result before returning to the caller. How do I allow the code to return control to the UI (freeing up the GUI) whilst getting the eventual result from the asynchronous Task?

Also, from within a Task can I legitimately do MessageBox.Show("Some message")? This does not work for BackgroundWorkers and this pooled-thread is a background thread by default; yet it does not seem to be a problem. Thanks for your time.

回答1:

You are right, this is where the waiting happens:

 return asyncTestConn.Result;

You could simply build the finish-up code in the tail of TestConnection() or use a Continuation:

// untested
//Task<bool> asyncTestConn = Task.Factory.Create<bool> (
Task<bool> asyncTestConn = new Task<bool> (
    () => TestConnection(conn, bShowErrMsg));
asyncTestConn.ContinueWith(MyFinishCode);
asyncTestConn.Start()

can I legitimately do MessageBox.Show("Some message")?

Actually Yes, the MessageBox is thread-safe. Should be possible from a Bgw as well.

But you are prolonging the life of the Task by a lot, it's not a good idea.



回答2:

For the TPL, ContinueWith is exactly what you want. Expanding on Henk's answer:

var asyncTestConn = Task.Factory.StartNew(() => TestConnection(conn, bShowErrMsg));
// Henk's "MyFinishCode" takes a parameter representing the completed
// or faulted connection-testing task.
// Anything that depended on your "return asyncTestConn.Result;" statement
// needs to move into the callback method.
asyncTestConn.ContinueWith(task =>
    {
        switch (task.Status)
        {
            // Handle any exceptions to prevent UnobservedTaskException.
            case TaskStatus.Faulted: /* Error-handling logic */ break;
            case TaskStatus.RanToCompletion: /* Use task.Result here */ break;
        }
    },
    // Using this TaskScheduler schedules the callback to run on the UI thread.
    TaskScheduler.FromCurrentSynchronizationContext());