Рассмотрите приложение Winforms, где у нас есть кнопка, которая генерирует некоторые результаты. Если пользователь нажимает кнопку второй раз, он должен отменить первый запрос для генерации результатов и начать новый.Условие гонки с CancellationToken, где CancellationTokenSource отменяется только в основной теме
Мы используем рисунок ниже, но мы не уверены, нужен ли какой-либо код для предотвращения состояния гонки (см. Прокомментированные строки).
private CancellationTokenSource m_cts;
private void generateResultsButton_Click(object sender, EventArgs e)
{
// Cancel the current generation of results if necessary
if (m_cts != null)
m_cts.Cancel();
m_cts = new CancellationTokenSource();
CancellationToken ct = m_cts.Token;
// **Edit** Clearing out the label
m_label.Text = String.Empty;
// **Edit**
Task<int> task = Task.Run(() =>
{
// Code here to generate results.
return 0;
}, ct);
task.ContinueWith(t =>
{
// Is this code necessary to prevent a race condition?
// if (ct.IsCancellationRequested)
// return;
int result = t.Result;
m_label.Text = result.ToString();
}, ct, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
}
Примечание:
- Мы только когда-либо отменить
CancellationTokenSource
на главном потоке. - Мы используем те же самые
CancellationToken
в продолжении, как и в исходной задаче.
Мы интересно, действительно ли возможна следующая последовательность событий:
- Пользователь нажимает кнопку «генерировать результаты» кнопку. Начальная задача t1 запускается.
- Пользователь снова нажимает кнопку «сгенерировать результаты». Сообщение Windows отправляется в очередь, но обработчик еще не выполнен.
- Задача t1 отделки.
- TPL
начинаетсяготовится к старту продолжение (так какCancellationToken
еще не аннулировано). Планировщик задач отправляет работу в очередь сообщений Windows (чтобы запустить ее в основном потоке). - Производится генерацияResultsButton_Click для второго щелчка и
CancellationTokenSource
отменяется. - Работа продолжения начинается, и она работает так, как будто токен не отменяется (т. Е. Отображает его результаты в пользовательском интерфейсе).
Так что, я думаю, что вопрос сводится к тому:
Когда работа размещена в главном потоке (с помощью TaskScheduler.FromCurrentSynchronizationContext()
) делает TPL проверить CancellationToken
на главном потоке перед выполнением действия выполнения задачи, или проверяет ли токен отмены на любом потоке, на котором это происходит, а затем отправляет работу на SynchronizationContext
?
Я не уверен, о каком «состоянии гонки» вы говорите. Но нет, вам не нужна дополнительная проверка на отмену, потому что вы использовали опцию 'TaskContinuationOptions.OnlyOnRanToCompletion'. –
Мы хотим отменить обновление пользовательского интерфейса даже в том случае, когда задача завершена, но пользователь отменил (путем повторного нажатия кнопки).Я обновил код, чтобы включить очистку ярлыка пользовательского интерфейса, чтобы лучше показать проблему. Как только пользователь нажимает кнопку, мы хотим, чтобы метка была пустой, пока не отобразятся результаты для этого щелчка. –
После продолжения продолжения вы находитесь в потоке пользовательского интерфейса (из-за 'TaskScheduler.FromCurrentSynchronizationContext'). Нить пользовательского интерфейса не может делать ничего другого, пока это продолжение не будет выполнено (один поток, по одному в момент времени). Таким образом, в случае 4/5/6 5 будет * не * происходить до тех пор, пока продолжение не будет завершено (т.е. это не 5, это действительно 6) –