正确取消异步操作并再次触发 [英] Correctly cancel async operation and fire it again

查看:24
本文介绍了正确取消异步操作并再次触发的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何处理用户可能多次点击按钮的情况,这会调用长时间运行的异步操作.

How to handle case, where user might hit the button, which invokes long running async operation, multiple time.

我的想法是首先检查异步操作是否正在运行,取消它并再次触发它.

My idea was first check if the async operation is running, cancel it and fire it again.

到目前为止,我已经尝试使用 CancellationTokenSource 构建这种功能,但它没有按预期工作.有时有两个异步操作正在运行,所以当我开始新的操作时,旧的"异步操作还没有被取消,这会混淆结果处理.

So far I have tried to build this kind of functionality using CancellationTokenSource, but it is not working as expected. Some times there is two async operations running, so the "old" async oprations is not cancelled yet when I start new one and this mixes up the resul handling.

有什么建议或例子来处理这种情况吗?

Any suggestions or examples how to handle this kind of case?

public async void Draw()
{
    bool result = false;

    if (this.cts == null)
    {
        this.cts = new CancellationTokenSource();

        try
        {
            result = await this.DrawContent(this.TimePeriod, this.cts.Token);
        }
        catch (Exception ex)
        {}
        finally
        {
            this.cts = null;
        }
    }

    else
    {
        this.cts.Cancel();
        this.cts = new CancellationTokenSource();

        try
        {
            result = await this.DrawContent(this.TimePeriod, this.cts.Token);
        }
        catch (Exception ex)
        {}
        finally
        {
            this.cts = null;
        }
    }

}

最后,我认为在短时间内运行两个异步操作也不错(当新的被触发但旧的尚未取消时).

In the end, I think it is not bad that there is two async operations running in short time (when the new is fired but the old one is not yet cancelled).

这里真正的问题是我如何为最终用户显示进度.当旧的异步操作结束时,它会向最终用户隐藏进度指示器,但新触发的异步操作仍在运行.

The real problem here is how I display the progress for enduser. As when the old async operation ends, it hides the progress indicator from enduser but the newly fired async operation is still running.

在 DrawContent(...) 中,我使用了 ThrowIfCancellationRequested,因此取消正在运行的任务似乎可以正常工作.

Inside DrawContent(...) I use ThrowIfCancellationRequested, so cancelling the running task seems to work ok.

关于进度显示.当调用 Draw() 时,我将加载指示器设置为可见,当此方法结束时,我隐藏加载指示器.所以现在当我开始新的异步操作后取消之前的异步操作时,我的加载指示器被设置为隐藏.当旧"方法结束时,如果还有另一个异步方法仍在运行,我应该如何跟踪.

About progress display. When the Draw() is called, I set loading indicator visible and when this method ends, I hide loading indicator. So now when the previous async operation is cancelled after I start new one, my loading indicator is set hidden. How should I keep track if there is another async method still running when the "old" one ends.

推荐答案

我想借此机会细化一些相关代码.在你的情况下,它可以像下面那样使用.

I'd like to take a chance to refine some related code. In your case, it can be used like below.

请注意,如果挂起操作的前一个实例失败(抛出了除 OperationCanceledException 以外的任何内容),您仍会看到它的错误消息.这种行为很容易改变.

Note, if the previous instance of the pending operation has failed (thrown anything other than OperationCanceledException), you'll still see an error message for it. This behavior can be easily changed.

如果在操作结束时它仍然是任务的最新实例,它只会隐藏进度 UI:if (thisTask == _draw.PendingTask) _progressWindow.Hide();

It only hides the progress UI if by the end of the operation if it's still the most recent instance of the task: if (thisTask == _draw.PendingTask) _progressWindow.Hide();

此代码不是线程安全的(_draw.RunAsync 不能同时调用),并且设计为从 UI 线程调用.

This code is not thread-safe as is (_draw.RunAsync can't be called concurrently), and is designed to be called from a UI thread.

Window _progressWindow = new Window();

AsyncOp _draw = new AsyncOp();

async void Button_Click(object s, EventArgs args)
{
    try
    {
        Task thisTask = null;
        thisTask = _draw.RunAsync(async (token) =>
        {
            var progress = new Progress<int>(
                (i) => { /* update the progress inside progressWindow */ });

            // show and reset the progress
            _progressWindow.Show();
            try
            {
                // do the long-running task
                await this.DrawContent(this.TimePeriod, progress, token);
            }
            finally
            {
                // if we're still the current task,
                // hide the progress 
                if (thisTask == _draw.PendingTask)
                    _progressWindow.Hide();
            }
        }, CancellationToken.None);
        await thisTask;
    }
    catch (Exception ex)
    {
        while (ex is AggregateException)
            ex = ex.InnerException;
        if (!(ex is OperationCanceledException))
            MessageBox.Show(ex.Message);
    }
}

class AsyncOp
{
    Task _pendingTask = null;
    CancellationTokenSource _pendingCts = null;

    public Task PendingTask { get { return _pendingTask; } }

    public void Cancel()
    {
        if (_pendingTask != null && !_pendingTask.IsCompleted)
            _pendingCts.Cancel();
    }

    public Task RunAsync(Func<CancellationToken, Task> routine, CancellationToken token)
    {
        var oldTask = _pendingTask;
        var oldCts = _pendingCts;

        var thisCts = CancellationTokenSource.CreateLinkedTokenSource(token);

        Func<Task> startAsync = async () =>
        {
            // await the old task
            if (oldTask != null && !oldTask.IsCompleted)
            {
                oldCts.Cancel();
                try
                {
                    await oldTask;
                }
                catch (Exception ex)
                {
                    while (ex is AggregateException)
                        ex = ex.InnerException;
                    if (!(ex is OperationCanceledException))
                        throw;
                }
            }
            // run and await this task
            await routine(thisCts.Token);
        };

        _pendingCts = thisCts;

        _pendingTask = Task.Factory.StartNew(
            startAsync,
            _pendingCts.Token,
            TaskCreationOptions.None,
            TaskScheduler.FromCurrentSynchronizationContext()).Unwrap();

        return _pendingTask;
    }
}

这篇关于正确取消异步操作并再次触发的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆