c# - 一种自取消和重启任务的模式

标签 c# .net asynchronous task-parallel-library async-await

是否有推荐的自取消和重新启动任务的既定模式?

例如,我正在为后台拼写检查器开发 API。拼写检查 session 被包装为 Task。每个新 session 都应取消前一个 session 并等待其终止(以正确地重新使用拼写检查服务提供商等资源)。

我想出了这样的事情:

class Spellchecker
{
    Task pendingTask = null; // pending session
    CancellationTokenSource cts = null; // CTS for pending session

    // SpellcheckAsync is called by the client app
    public async Task<bool> SpellcheckAsync(CancellationToken token)
    {
        // SpellcheckAsync can be re-entered
        var previousCts = this.cts;
        var newCts = CancellationTokenSource.CreateLinkedTokenSource(token);
        this.cts = newCts;

        if (IsPendingSession())
        {
            // cancel the previous session and wait for its termination
            if (!previousCts.IsCancellationRequested)
                previousCts.Cancel();
            // this is not expected to throw
            // as the task is wrapped with ContinueWith
            await this.pendingTask; 
        }

        newCts.Token.ThrowIfCancellationRequested();
        var newTask = SpellcheckAsyncHelper(newCts.Token);

        this.pendingTask = newTask.ContinueWith((t) => {
            this.pendingTask = null;
            // we don't need to know the result here, just log the status
            Debug.Print(((object)t.Exception ?? (object)t.Status).ToString());
        }, TaskContinuationOptions.ExecuteSynchronously);

        return await newTask;
    }

    // the actual task logic
    async Task<bool> SpellcheckAsyncHelper(CancellationToken token)
    {
        // do not start a new session if the the previous one still pending
        if (IsPendingSession())
            throw new ApplicationException("Cancel the previous session first.");

        // do the work (pretty much IO-bound)
        try
        {
            bool doMore = true;
            while (doMore)
            {
                token.ThrowIfCancellationRequested();
                await Task.Delay(500); // placeholder to call the provider
            }
            return doMore;
        }
        finally
        {
            // clean-up the resources
        }
    }

    public bool IsPendingSession()
    {
        return this.pendingTask != null &&
            !this.pendingTask.IsCompleted &&
            !this.pendingTask.IsCanceled &&
            !this.pendingTask.IsFaulted;
    }
}

客户端应用程序(UI)应该能够根据需要多次调用 SpellcheckAsync,而不必担心取消挂起的 session 。主 doMore 循环在 UI 线程上运行(因为它涉及 UI,而所有拼写检查服务提供程序调用都是 IO 绑定(bind)的)。

我不得不将 API 分成两部分,SpellcheckAsyncSpellcheckAsyncHelper,这让我感到有些不自在,但我想不出更好的方法这样做,还有待测试。

最佳答案

我认为总体概念很好,但我建议您不要使用 ContinueWith

我只是使用常规的 await 编写它,并且不需要很多“我已经在运行了吗”逻辑:

Task pendingTask = null; // pending session
CancellationTokenSource cts = null; // CTS for pending session

// SpellcheckAsync is called by the client app on the UI thread
public async Task<bool> SpellcheckAsync(CancellationToken token)
{
    // SpellcheckAsync can be re-entered
    var previousCts = this.cts;
    var newCts = CancellationTokenSource.CreateLinkedTokenSource(token);
    this.cts = newCts;

    if (previousCts != null)
    {
        // cancel the previous session and wait for its termination
        previousCts.Cancel();
        try { await this.pendingTask; } catch { }
    }

    newCts.Token.ThrowIfCancellationRequested();
    this.pendingTask = SpellcheckAsyncHelper(newCts.Token);
    return await this.pendingTask;
}

// the actual task logic
async Task<bool> SpellcheckAsyncHelper(CancellationToken token)
{
    // do the work (pretty much IO-bound)
    using (...)
    {
        bool doMore = true;
        while (doMore)
        {
            token.ThrowIfCancellationRequested();
            await Task.Delay(500); // placeholder to call the provider
        }
        return doMore;
    }
}

关于c# - 一种自取消和重启任务的模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18999827/

相关文章:

c# - 替换 WPF 中的部分默认模板

.net - 客户端验证的作用与 .NET 页面验证器/XSS 预防相同吗?

c# - 类 Evidence C# 的 AddAssemblyEvidence() 的参数

java - 保证消息异步传送

c# - 关于c#中的线程

c# - EntityFramework Core MySQL 脚本-迁移导出定制

c# - 表单提交后启用文本框和按钮 ASP.NET MVC 4

c# - LinqToSql - 不允许在查询中显式构造实体类型 '{0}'

python - 如何处理多个gRPC future?

javascript - 迷失在 Node、Express 和谷歌分析 API 的回调 hell 中