具有取消能力的长时间运行模式

为了执行长时间运行(让它在此上下文中搜索)操作,我将加载逻辑放在TPL任务中,因此在后台线程上调用通用方法Search()Search()操作可以足够长,因此我需要能够使用CancellationToken正确取消它。 但是Search()操作直到完成才返回,所以我必须做一些逻辑才能实现方便和(!)快速取消。

使用WaitHandle我可以实现这样的事情:

private void StartSearch() // UI thread { CancellationTokenSource s = new CancellationTokenSource(); Task.Factory.StartNew(() => StartSearchInternal(s.Token), s.Token) } private void StartSearchInternal(CancellationToken token) // Main Background Thread { ManualResetEvent eHandle = new ManualResetEvent(false); Task.Factory.StartNew(() => Search(eHandle ), TaskScheduler.Default); WaitHandle.WaitAny(new [] { eHandle, token.WaitHandle }); token.ThrowIfCancellationRequested(); } private IEnumerable Search(ManualResetEvent e) // Another Background thread { try { // Real search call, ie to database, service, or AD, doesn't matter return RealSearch(); } catch {} // Here, for simplicity of question, catch and eat all exceptions finally { try { e.Set(); } catch {} } } 

在我看来,这不是那么优雅的解决方案,可以制作。

问:这项任务还有其他方法吗?

这是我的评论重构为包含代码的答案。 它包含几个使用Task.Wait和异步模式的替代方案,其选择取决于您是否从UI线程调用该方法。

对O / P和其他答案有几点评论,其中包含有关异步行为的有价值信息。 请阅读这些,因为下面的代码有很多“改进的机会”。

 using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace SearchAgent { class CancellableSearchAgent { // Note: using 'object' is a bit icky - it would be better to define an interface or base class, // or at least restrict the type of object in some way, such as by making CancellableSearchAgent // a template CancellableSearchAgent and replacing every copy of the text 'object' in this // answer with the character 'T', then make sure that the RealSearch() method return a collection // of objects of type T. private Task> _searchTask; private CancellationTokenSource _tokenSource; // You can use this property to check how the search is going. public TaskStatus SearchState { get { return _searchTask.Status; } } // When the search has run to completion, this will contain the result, // otherwise it will be null. public IEnumerable SearchResult { get; private set; } // Create a new CancellableSearchAgent for each search. The class encapsulates the 'workflow' // preventing issues with null members, re-using completed tasks, etc, etc. // You can add parameters, such as SQL statements as necessary. public CancellableSearchAgent() { _tokenSource = new CancellationTokenSource(); _searchTask = Task>.Factory.StartNew(() => RealSearch(), TaskScheduler.Default); } // This method can be called from the UI without blocking. // Use this if the CancellableSearchAgent is part of your ViewModel (Presenter/Controller). public async void AwaitResultAsync() { SearchResult = await _searchTask; } // This method can be called from the ViewModel (Presenter/Controller), but will block the UI thread // if called directly from the View, making the UI unresponsive and unavailable for the user to // cancel the search. // Use this if CancellableSearchAgent is part of your Model. public IEnumerable AwaitResult() { if (null == SearchResult) { try { _searchTask.Wait(_tokenSource.Token); SearchResult = _searchTask.Result; } catch (OperationCanceledException) { } catch (AggregateException) { // You may want to handle other exceptions, thrown by the RealSearch() method here. // You'll find them in the InnerException property. throw; } } return SearchResult; } // This method can be called to cancel an ongoing search. public void CancelSearch() { _tokenSource.Cancel(); } } } 

如果您可以控制StartSearchInternal()Search(eHandle) ,那么您应该可以在Search Core循环中使用ThrowIfCancellationRequested进行协作取消。

有关更多详细信息,我强烈建议您阅读本文档: “在.NET Framework 4中使用取消支持” 。

另外,您应该在ViewModel类的某个位置存储对Task.Factory.StartNew(() => StartSearchInternal(s.Token), s.Token)返回的任务的引用。 您很可能希望观察其结果以及可能抛出的任何exception。 您可能想要检查Lucian Wischik的“Async re-entrancy,以及处理它的模式” 。