[英]Pattern for long-running operation with cancellation ability
為了執行長時間運行(在此上下文中為搜索)操作,我將加載邏輯放入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<object> Search(ManualResetEvent e) // Another Background thread
{
try
{
// Real search call, i.e. 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 {}
}
}
在我看來,這不是一個可以解決的優雅解決方案。
問:還有其他方法可以完成此任務嗎?
如果您可以控制StartSearchInternal()
和Search(eHandle)
,那么您應該能夠在Search
核心循環內使用ThrowIfCancellationRequested
進行協作取消。
有關更多詳細信息,我強烈建議閱讀此文檔: “在.NET Framework 4中使用取消支持” 。
附帶說明,您可能應該在ViewModel類中的某個位置存儲對Task.Factory.StartNew(() => StartSearchInternal(s.Token), s.Token)
返回的任務的引用。 您最有可能希望觀察其結果以及它可能引發的任何異常。 您可能要檢查Lucian Wischik的“異步重新進入以及處理它的模式” 。
這是我的評論,重構為包含代碼的答案。 它包含一些使用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<T> 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<IEnumerable<object>> _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<object> 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<IEnumerable<object>>.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<object> 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();
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.