Correctly cancel async operation and fire it again

后端 未结 3 776
走了就别回头了
走了就别回头了 2020-12-18 11:37

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 run

3条回答
  •  没有蜡笔的小新
    2020-12-18 12:30

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

    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.

    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();

    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(
                    (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 routine, CancellationToken token)
        {
            var oldTask = _pendingTask;
            var oldCts = _pendingCts;
    
            var thisCts = CancellationTokenSource.CreateLinkedTokenSource(token);
    
            Func 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;
        }
    }
    

提交回复
热议问题