簡體   English   中英

使用 CancellationToken 異步搜索文件

[英]Asynchronous searching for files with CancellationToken

我正在嘗試構建一個 function 來搜索目錄中的文件並將它們添加到ObservableCollection

此任務應異步運行,以便 UI 保持響應並在再次執行該方法時隨時停止搜索。 以便清除ObservableCollection並重新開始搜索。

我的問題是,當任務仍在運行時,我不知道如何以及何時以相同的方法准確地取消 CancellationTokenSource。
另外,我不知道如何在 function 上使用 Async 和 Await,因為它分為三種方法。

現在它看起來像這樣:

搜索 Function:

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

將文件添加到 ObservableCollection:

我把它放到一個自己的方法中,因為它在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);
    }));
}

您的代碼有幾個問題:

  1. 您處置CancellationTokenSource ,但在下次調用SearchAsync()時,您在處置的 CTS 上調用.Cancel() 這個調用當然失敗了。
  2. 您在FindFiles()中使用async void 你的await Task.Run(() => FindFiles(token)); 因為它立即退出。 除非絕對必要,否則永遠不要使用async void
  3. 您可能有競爭條件,具體取決於您的任務調度程序是否是多線程的。

看看這個實現:

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM