Correctly cancel async operation and fire it again

后端 未结 3 758
走了就别回头了
走了就别回头了 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:28

    Calling cts.Cancel() will not automatically stop a Task. Your Task needs to actively check whether cancellation has been requested. You can do something like this:

    public async Task DoStuffForALongTime(CancellationToken ct)
    {
        while (someCondition)
        {
            if (ct.IsCancellationRequested)
            {
                return;
            }
    
            DoSomeStuff();
        }
    }
    
    0 讨论(0)
  • 2020-12-18 12:28

    Why not follow the BackgroundWorker pattern and break out of the loop in DrawContent?

    private bool _cancelation_pennding=false;
    private delegate DrawContentHandler(TimePeriod period, Token token)
    private DrawContentHandler _dc_handler=null;
    
    .ctor(){
        this._dc_handler=new DrawContentHandler(this.DrawContent)
    }
    public void CancelAsync(){
        this._cancelation_pennding=true;
    }
    public void Draw(){
        this._dc_handler.BeginInvoke(this.TimePeriod, this.cts.Token)
    }
    private void DrawContent(TimePeriod period, Token token){
        loop(){
            if(this._cancelation_pennding)
            {
                break;
            }
    
            //DrawContent code here
        }
        this._cancelation_pennding=false;
    }
    
    0 讨论(0)
  • 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<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;
        }
    }
    
    0 讨论(0)
提交回复
热议问题