Предотвращение зависания окна (WPF) при использовании TPL - PullRequest
2 голосов
/ 31 мая 2011

Я создаю WPF, в котором есть кнопка для выполнения запроса sql на сервере sql (выполнение запроса может занять много времени).Я хочу использовать TPL для этого.

Этот код: var result = Task.Factory.StartNew (() => {command.ExecuteNonQuery ();});

дает это исключение: ExecuteNonQuery требует открытого и доступного соединения.Текущее состояние соединения закрыто.

Я полагаю, это связано с тем, что запрос выполняется в другом потоке и не знает об открытом соединении.

У меня 2 вопроса: 1Как я могу сообщить новому потоку об этом открытом соединении?2. После решения этого вопроса, как мне заставить окно не зависать из-за этого запроса.

Спасибо

Ответы [ 2 ]

6 голосов
/ 31 мая 2011

Вам нужно будет создать и открыть соединение для этой команды в теле Задачи. Либо так, либо не закрывайте соединение за пределами Задачи, что, как я полагаю, именно то, что вы делаете здесь, но не можете сказать по одной строке кода, которую вы вставили.

Я бы лично сделал все это в теле задачи. Почему пользователь должен ждать, пока вы даже не настроите соединение / команду, если им это не нужно? Также есть вероятность, что ваше соединение является общим экземпляром, и оно не будет работать между потоками.

Как только вы включите работу с БД в задачу, по умолчанию она будет выполняться в потоке потоков пула, что освободит поток диспетчера WPF для возврата к обработке событий пользовательского интерфейса, предотвращая «зависание». Скорее всего, вы захотите обновить пользовательский интерфейс после выполнения этой задачи БД и сделать это, просто добавив задачу продолжения, но чтобы иметь возможность манипулировать пользовательским интерфейсом из этой задачи продолжения, необходимо убедиться, что она явно запланирована на запустить на ветке Dispatcher. Это делается путем явного указания TaskScheduler для текущего контекста синхронизации при планировании продолжения. Это будет выглядеть примерно так:

Task backgroundDBTask = Task.Factory.StartNew(() =>
{
    ... DB work here ...
});

backgroundDBTask.ContinueWith((t) =>
{
    ... UI update work here ...
},
TaskScheduler.FromCurrentSynchronizationContext());

Волшебство здесь заключается в использовании метода TaskScheduler::FromCurrentSynchronizationContext, который запланирует выполнение продолжения в потоке Dispatcher текущего вызова.

1 голос
/ 20 июня 2016

В дополнение к @ Дрю Марш ответ,

Чтобы избежать исключения:

Текущий SynchronizationContext нельзя использовать в качестве TaskScheduler

Вы можете использовать проверку наличия контента синхронизации:

private static TaskScheduler GetSyncronizationContent() => 
     SynchronizationContext.Current != null ? 
          TaskScheduler.FromCurrentSynchronizationContext() : 
          TaskScheduler.Current;

И используйте вместо этого:

Task backgroundDBTask = Task.Factory.StartNew(() =>
{
    //... DB work here ...
});

backgroundDBTask.ContinueWith((t) =>
{
    //... UI update work here ...
},
GetSyncronizationContent());
...