在async / await中取消线程

时间:2014-07-24 21:13:45

标签: c# .net async-await

我正在尝试使用await / async轮询功能来测试使用async / await处理事务的小应用程序。

我的测试设置:

    [TestMethod]
    public void TestProcessTimerOnly()
    {
        // this initializes and kicks off the polling
        var tp = new TransactionProcessor();

        try
        {
            Thread.Sleep(5000);
            tp.CancelProcessing();
        }
        catch (Exception ex)
        {
            LogErrors(ref tp, ex);
        }
        finally
        {
            DisplayLog(tp);
        }
    }

    [TestMethod]
    public void TestProcessTimerOnlyForcedCancellation()
    {
        // this initializes and kicks off the polling
        var tp = new TransactionProcessor(1);

        try
        {
            Thread.Sleep(5000);
            tp.CancelProcessing();
        }
        catch (Exception ex)
        {
            LogErrors(ref tp, ex);
        }
        finally
        {
            DisplayLog(tp);
        }
    }

我的代码(全部在一个班级中):

    // Constructor
    public TransactionProcessor(int? debugForcedCancellationDelay = null)
    {
// >>>>>>>> Setup Cancellation <<<<<<<<
        if(debugForcedCancellationDelay.IsEmpty() || debugForcedCancellationDelay.IsZeroOrLess())
            _cancellationToken = new CancellationTokenSource();
        else
            _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(debugForcedCancellationDelay.Value));
// >>>>>>>> End <<<<<<<<

// was:
        // RepeatActionEvery(() => TestingLog.Add("Repeat Action Every 1 Second"), TimeSpan.FromSeconds(1), _cancellationToken.Token).Wait();
// corrected:
        // _processTask is defined as a global field of type Task...
        _processTask = RepeatActionEvery(() => TestingLog.Add("Repeat Action Every 1 Second"), TimeSpan.FromSeconds(1), _cancellationToken.Token);    //.Wait();
    }

// was:
    //public void CancelProcessing()
// corrected:
    public async Task CancelProcessing()
    {
        _cancellationToken.Cancel();
        await _processTask;
    }
    public static async Task RepeatActionEvery(Action action, TimeSpan interval, CancellationToken cancellationToken)
    {
        while (true)
        {
            action();
            var task = Task.Delay(interval, cancellationToken);

            try { await task; }
            catch (TaskCanceledException) { return; }
        }
    }

当我运行TestProcessTimerOnly()测试时,它会坐在那里直到我最终取消测试装备。

当我运行TestProcessTimerOnlyForcedCancellation()测试时,它的行为符合预期。

所以问题归结为:我是否正确使用_cancellationToken变量?在一个实例中,我使用超时参数初始化。在另一个例子中,我初始化它没有参数。我在这里做错了什么?

2 个答案:

答案 0 :(得分:6)

您非常不鼓励使用"sync over async"

你在做什么

您正在从RepeatActionEvery获取任务,该任务仅在取消令牌取消时结束。但是你正在等待该任务的同步(阻止),这意味着你永远不会离开构造函数并到达取消令牌的行(tp.CancelProcessing();)。

当然,当您使用超时创建CancellationTokenSource时,它将最终取消自己而不必调用它,因此任务将结束,等待它的线程将可以自由地完成构造函数并调用tp.CancelProcessing();

你想做什么

你应该做的事情(IIUC)是存储交易任务而不等待它,只有当你取消(或完成)交易时才等待:

public TransactionProcessor(int? debugForcedCancellationDelay = null)
{
    // ...
    Task = RepeatActionEvery(
        () => TestingLog.Add("Repeat Action Every 1 Second"), 
        TimeSpan.FromSeconds(1), 
        _cancellationToken.Token);
}

public async Task CancelProcessingAsync()
{
    _cancellationToken.Cancel();
    await Task;
}

答案 1 :(得分:-2)

原因是因为在第一个示例中,您已使用.Wait()阻止所有线程,因此实际上没有发生任何事情,因为它等待了。它在第二个示例中起作用的原因是因为您已将cancelToken设置为在1秒后取消。这个取消令牌部分的“等待”是在另一个线程上,所以它仍然可以运行。

使用.Wait或Thread.Sleep将阻止所有线程。由于你的所有逻辑都在同一个线程上,所以你被困住了。使用Task.Run()。在RepeatEveryAction(()=&gt;)调用周围的Wait()(不带.Wait())应该清除它,因为逻辑将被放到另一个线程上运行。