正确取消异步操作并再次触发

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

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

到目前为止,我已尝试使用CancellationTokenSource构建此类function,但它没有按预期工作。 有时会运行两个异步操作,因此当我启动新的操作时,“旧的”异步操作不会被取消,这会混合处理结果。

任何建议或例子如何处理这种情况?

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; } } } 

编辑:最后,我认为有两个异步操作在短时间内运行(当新的被激活但旧的尚未被取消时)并不坏。

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

EDIT2:Inside DrawContent(…)我使用ThrowIfCancellationRequested,因此取消正在运行的任务似乎工作正常。

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

我想借此机会改进一些相关的代码 。 在您的情况下,它可以像下面这样使用。

注意,如果挂起操作的先前实例失败(抛出除OperationCanceledException之外的任何内容),您仍将看到它的错误消息。 这种行为很容易改变。

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

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

 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; } } 

调用cts.Cancel()不会自动停止任务。 您的任务需要主动检查是否已请求取消。 你可以这样做:

 public async Task DoStuffForALongTime(CancellationToken ct) { while (someCondition) { if (ct.IsCancellationRequested) { return; } DoSomeStuff(); } } 

为什么不遵循BackgroundWorker模式并在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; }