TPL Как выполнить «обратный вызов»

У меня есть небольшое приложение, которому необходимо проверить строки подключения SQL для нескольких подключений (каждое выполняется по одному). Для этого я временно установил ConnectionTimeout = 5, чтобы избежать длительного ожидания, если соединение недействительно, и ConnectionTimeout = 0 (ожидание навсегда), скажем.

Чтобы избежать зависания пользовательского интерфейса, когда мы пытаемся Open() установить плохое соединение (даже при ConnectionTimeout = 5 ожидание SqlException может составлять до двадцати секунд), я хочу запустить тест в отдельном потоке с помощью библиотеки параллельных задач (TPL). Итак, я создаю свой новый поток, например:

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

Проблема в том, что это по-прежнему блокирует пользовательский интерфейс (очевидно), поскольку он ожидает результата, прежде чем вернуться к вызывающей стороне. Как разрешить коду возвращать управление пользовательскому интерфейсу (освобождая графический интерфейс), получая конечный результат от асинхронного Task?

Кроме того, из Task могу ли я на законных основаниях сделать MessageBox.Show("Some message")? Это не работает для BackgroundWorkers, и этот объединенный поток по умолчанию является фоновым потоком; но это не кажется проблемой. Спасибо за ваше время.


person MoonKnight    schedule 08.03.2012    source источник


Ответы (2)


Вы правы, вот где происходит ожидание:

 return asyncTestConn.Result;

Вы можете просто создать завершающий код в конце TestConnection() или использовать продолжение:

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

могу ли я на законных основаниях сделать MessageBox.Show("Some message")?

На самом деле да, MessageBox является потокобезопасным. Должно быть возможно и от Bgw.

Но вы сильно продлеваете жизнь Задаче, это не очень хорошая идея.

person Henk Holterman    schedule 08.03.2012
comment
Большое спасибо за ваш ответ. Вся идея состоит в том, чтобы как можно скорее вернуться к передаче управления пользовательскому/графическому интерфейсу, как это можно сделать/может достичь этого? Если я вызываю приведенный выше код из метода с именем ParrTestConn(SqlConnection conn, string bShowErrMsg), то я не могу сказать asyncTestConn.ContinueWith(ParrTestConn(conn, bShowErrMsg))... Или могу? - person MoonKnight; 08.03.2012
comment
@Killer Вы можете просто вернуться после Start(). Графический интерфейс должен оставаться отзывчивым при использовании этого. - person Henk Holterman; 08.03.2012
comment
У меня нет метода Task.Factory.Create<T>? - person MoonKnight; 08.03.2012

Для TPL ContinueWith — это именно то, что вам нужно. Расширение ответа Хенка:

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());
person anton.burger    schedule 08.03.2012
comment
Действительно полезно. Спасибо. Сочетание двух ответов абсолютно мятное. - person MoonKnight; 08.03.2012