Подождите, пока задача отменится, ждет навсегда

У меня есть форма, в которой я запускаю задачу для загрузки содержимого. Если пользователь нажимает «Отменить», эту задачу, конечно, необходимо отменить. Но, кажется, я делаю что-то не так. Форма никогда не закрывается и продолжает ждать задачи:

public partial class Designer : Form
{
    private CancellationTokenSource _cancellationTokenSource;
    private Task _loadTask;

    private async void Designer_Shown(object sender, EventArgs e)
    {
        _cancellationTokenSource = new CancellationTokenSource();
        try
        {
            _loadTask= Workbench.Instance.CurrentPackage.LoadObjects(_cancellationTokenSource.Token);
            await _loadTask;
        }
        catch (Exception ex)
        {
            Debug.Print(ex.ToString());
        }
    }

    private void btnCancel_Click(object sender, EventArgs e)
    {
        _cancellationTokenSource.Cancel();
        _loadTask.Wait(); //Waits forever
        this.DialogResult = DialogResult.Cancel;
        this.Close();
    }
}

Где моя вина?

Изменить код LoadObjects()

public Task LoadObjects(CancellationToken cancelToken)
{
    return Task.Run(() =>
    {
        LoadParameters(cancelToken);
        LoadConditionChecks(cancelToken);
        LoadConditonRules(cancelToken);
        LoadOperations(cancelToken);
    }, cancelToken);
}

Я передаю токен подметодам, так как циклы действительно существуют...


person Marco Rebsamen    schedule 04.08.2017    source источник
comment
Для отмены задачи требуется, чтобы запущенная задача проверила, была ли она отменена. Похоже, он этого не делает, но я не знаю, что такое Workbench.   -  person Equalsk    schedule 04.08.2017
comment
Вы контролируете код для Workbench.Instance.CurrentPackage.LoadObjects. Вы уверены, что он соблюдает токен отмены?   -  person Gusdor    schedule 04.08.2017
comment
Где-то в коде, который принимает CancellationToken, должна быть проверка на отмену, например CancellationToken.IsCancellationRequested или CancellationToken.ThrowIfCancellationRequested . Если ее нет, просто в самом конце вы получите такое поведение.   -  person Peter Bons    schedule 04.08.2017
comment
Я добавил код LoadObjects()   -  person Marco Rebsamen    schedule 04.08.2017


Ответы (2)


Вы попадаете в тупик в потоке пользовательского интерфейса, ожидая его и вызывая Task.Wait(). Избегайте Task.Wait любой ценой.

Обработайте результат отмены в асинхронном продолжении, например:

private async void btnCancel_Click(object sender, EventArgs e)
{
    _cancellationTokenSource.Cancel();
    await _loadTask;
    this.DialogResult = DialogResult.Cancel;
    this.Close();
}

Это единственный раз, когда async void приемлемо.

Мой любимый специалист по асинхронному коду Стивен Клири (Stephen Cleary) опубликовал блестящую запись в блоге, объясняющую, почему следует избегать использования Task.Wait и Task.Result в качестве механизмов блокировки - Не блокировать асинхронный код


Как бы то ни было, когда я отменяю задачу, я никогда не жду ее завершения. Я немедленно отвечаю на отмену и позволяю задаче завершиться в фоновом режиме. Это обеспечивает отзывчивый пользовательский интерфейс для пользователя. Если мне нужно получить результат от отмененной задачи, я добавлю некоторую работу с пользовательским интерфейсом, чтобы сообщить «ожидание окончания операции» после того, как пользователь нажмет «Отмена».

person Gusdor    schedule 04.08.2017
comment
Вот и все... Во всех примерах, которые я нашел с помощью Google, все они делали .Wait()вещь... - person Marco Rebsamen; 04.08.2017

Вы не используете токен отмены. Этот шаблон известен как совместная отмена задачи — и потребитель, и исполняемый код должны обслуживать отмену. Это поможет вам сохранить правильное состояние после отмены.

Решение - обновите свои методы LoadX():

void LoadParameters(cancelToken)
{
    ... // do some work

    cancelToken.ThrowIfCancellationRequested();

    ... // do some more work
}
person Gusdor    schedule 04.08.2017