可取消"火灾和遗忘"任务。这是不好的做法吗?我的实施是否正确?

时间:2017-01-05 11:31:19

标签: c# async-await cancellationtokensource

背景:我正在开发一个应用程序,它本质上是一个远程服务器的缓存文件资源管理器。当用户单击目录时,它将从目录树的本地副本向其显示其子目录。然后它也开始了一场火灾并忘记了#34;任务从服务器检索视图中的目录更改并更新缓存以及用户正在显示的内容。

private CancellationTokenSource _cts;
private SemaphoreSlim _myTaskBlocker = new SemaphoreSlim(1,1);

public void CancelMyTask()
{
    _cts?.Cancel();
}

public async Task FireAndForgetWithCancel()
{
    await _myTaskBlocker.WaitAsync();
    _cts = new CancellationTokenSource();

    try
    {
        //Some potentially long running code.
        token.ThrowIfCancellationRequested();
    }
    catch(OperationCancelledException){}
    finally
    {        
        _cts.dispose();
        _cts = null;
        _myTaskBlocker.Release();
    }
}

编辑1:这可以吗? SemaphoreSlim就像锁定_cts一样,所以在进行更改之前我不需要锁定它吗?

编辑2:所以我得出的结论是,这是一个坏主意,并且不能以我最初希望的方式完成。

我的解决方案是让ViewModel发送请求并侦听更新事件。

  public class Model
    {
        // Thread safe observable collection with AddRange.
        public ObservableCollectionEx<file> Files { get; }

        // A request queue.
        public ActionBlock<request> Requests { get; }
    }

    public class ViewModel
    {
        public ObservableCollectionEx<file> FilesTheUserSees { get; }

        public ViewModel()
        {
            Model.Files.CollectionChanged += FileCollectionChanged;
        }

        public async Task UserInstigatedEvent()
        {
            // Do some stuff to FilesTheUserSees here.

            // Request the model to check for updates. Blocks only as long as it takes to send the message.
            await Model.Requests.SendAsync(new request());
        }

        public void FileCollectionChanged(object sender, CollectionChangedEventArgs e)
        {
            // Check to see if there are any new files in Files.
            // If there are new files that match the current requirements add them to FilesTheUserSees.
        }
    }

需要注意的一些问题是,现在依赖ObservableCollectionsEx是线程安全的,但是这是一个可以实现的目标,即使它有一些缺点也更容易调试。

1 个答案:

答案 0 :(得分:3)

这不是CancellationToken取消过程的好用。您的代码有几个问题,您无法正确使用取消令牌:

  1. CancelMyTaskFireAndForgetWithCancel是相互依赖的,因为它们内部与CancellationTokenSource创建FireAndForgetWithCancel相关联,但CancelMyTask CancelMyTask 1}}使用。当FireAndForgetWithCancel起作用时,您只有一个小窗口,当取消时,您没有直接指示。
  2. 如果您多次致电CancelMyTaskFireAndForgetWithCancel将仅取消上一个任务,而其他任务将继续。
  3. 由于CancellationTokenSource创建了自己的CancellationToken并为自己保留FireAndForgetWithCancel,因此没有其他消费者可以使用该令牌,例如触发自己的取消或将其与其他令牌合并
  4. OperationCancelledException中,你抓住CancellationTokenSource而不是让它失败。这意味着任务永远不会处于取消状态。
  5. 正确的用法是接受取消令牌作为参数并让调用者处理取消,因为建议将CancellationTokenprivate SemaphoreSlim _myTaskBlocker = new SemaphoreSlim(1,1); public async Task FireAndForgetWithCancel(CancellationToken cancellationToken) { await _myTaskBlocker.WaitAsync(); try { //Some potentially long running code. cancellationToken.ThrowIfCancellationRequested(); } finally { _myTaskBlocker.Release(); } } 分开:

    CancellationTokenSource

    调用者将创建\s以获取取消令牌,或者它将通过现有令牌。这完全取决于来电者,最好留给它。