![](/img/trans.png)
[英]System.NotSupportedException: 'The use of 'System.Threading.CancellationToken' on the task-based asynchronous method is not supported.'
[英]Asynchronous searching for files with CancellationToken
我正在嘗試構建一個 function 來搜索目錄中的文件並將它們添加到ObservableCollection
。
此任務應異步運行,以便 UI 保持響應並在再次執行該方法時隨時停止搜索。 以便清除ObservableCollection
並重新開始搜索。
我的問題是,當任務仍在運行時,我不知道如何以及何時以相同的方法准確地取消 CancellationTokenSource。
另外,我不知道如何在 function 上使用 Async 和 Await,因為它分為三種方法。
現在它看起來像這樣:
CancellationTokenSource _tokenSource;
public async void SearchAsync()
{
Files.Clear();
FileCount = 0;
// Cancels Task if its running
if(_tokenSource != null)
{
_tokenSource.Cancel(); (throws Error right here. Task already disposed)
}
// Create new Cancellation Token
_tokenSource = new CancellationTokenSource();
CancellationToken token = _tokenSource.Token;
try
{
//Starts Task
await Task.Run(() => FindFiles(token));
}
catch (OperationCanceledException ex)
{
//When Canceled
MessageBox.Show("Test", "Test");
}
finally
{
//When finished
_tokenSource.Dispose();
}
}
private async Task FindFiles(CancellationToken token)
{
foreach (string filePath in Directory.EnumerateFiles(MainPath, "*.*", searchOption))
{
await AddFileAsync(filePath);
FileCount++;
if (token.IsCancellationRequested)
{
Files.Clear();
FileCount = 0;
token.ThrowIfCancellationRequested();
}
}
}
我把它放到一個自己的方法中,因為它在LoadFiles()
中被多次調用。 我只是簡化了它。
public ObservableCollection<FileModel> Files { get; set; }
private async Task AddFileAsync(string filePath)
{
await Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
{
FileModel file = new()
{
FileName = Path.GetFileName(filePath),
FilePath = filePath
};
Files.Add(file);
}));
}
您的代碼有幾個問題:
CancellationTokenSource
,但在下次調用SearchAsync()
時,您在處置的 CTS 上調用.Cancel()
。 這個調用當然失敗了。FindFiles()
中使用async void
。 你的await Task.Run(() => FindFiles(token));
因為它立即退出。 除非絕對必要,否則永遠不要使用async void
。看看這個實現:
public sealed class Searcher
{
// Initialized here to avoid null checks.
private CancellationTokenSource _tokenSource = new CancellationTokenSource();
public async Task SearchAsync()
{
var newTokenSource = new CancellationTokenSource();
var oldTokenSource = Interlocked.Exchange(ref _tokenSource, newTokenSource);
if (!oldTokenSource.IsCancellationRequested)
{
oldTokenSource.Cancel();
}
try
{
//Starts Task
await Task.Run(() => FindFiles(newTokenSource.Token));
}
catch (OperationCanceledException)
{
//When Canceled
}
}
private async Task FindFiles(CancellationToken token)
{
await Task.Delay(1000, token);
}
}
因此,現在對SearchAsync()
的每次調用都會跟蹤兩個 CTS:當前的和前一個。 這解決了問題 1。
我已將async void FindFiles
轉換為解決問題 2 的async Task FindFiles
。我還將SearchAsync()
的簽名更改為async Task
,以便調用者可以等待它。 雖然這取決於 function 的使用方式,但不是必需的。
最后我使用了 Interlocked.Exchange 來解決問題 3。這個調用是原子的。 另一種方法是使用鎖。
我已經刪除了 CTS 處理代碼,因為它並不容易。 主要問題是.Dispose()
應該在您完成 CTS 后調用。 因此,無論是任務被取消還是任務完成時。 問題是您無法輕松驗證 CTS 是否已被處理(之后它在無法使用的 state 中)。 或者也許我不知道如何正確地做到這一點。 處理 CTS 既奇怪又困難,請閱讀: 何時處理 CancellationTokenSource? 那里的大多數答案都聲稱,如果 CTS 沒有鏈接,那么 GC 將在某個時候正確收集和處理它。
一種方法是使用自定義 class 來包裝 CTS,以跟蹤此 CTS 是否已處置。 並且可能對.Cancel()
和.Dispose()
的調用被鎖包裹。 但隨后一切變得復雜。 另一種方法是在您訪問ObjectDisposedException
時捕獲它,但我不確定這是否正確。
另一方面,對於這個簡單的用例,我認為您不必擔心處理它。 讓 GC 收集並處理它。 CTS 和令牌都不會泄漏到外面,如果你記得讓它等待它應該沒問題。 我會根據這個注釋做到這一點:
筆記
始終在釋放對 CancellationTokenSource 的最后引用之前調用
Dispose
。 否則,在垃圾收集器調用 CancellationTokenSource 對象的Finalize
方法之前,它正在使用的資源不會被釋放。
對我來說,如果不是絕對需要立即處理,不處理它聽起來很好。 國際海事組織並非如此。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.