Task.Status应该运行但显示RanToCompletion

时间:2018-04-23 16:34:36

标签: c# uwp task

我很困惑这种情况,其中一个类有一个方法可以启动两个定期Task,然后一个属性用于检查两个Task是否仍在运行,但是结果出人意料。这是代码(简化):

public partial class UdpClientConnector
{        
    Task localListener;

    Task periodicSubscriber;

    bool keepWorking = false;

    public bool IsRunning
    {
        get
        {
            if ((localListener != null) && (periodicSubscriber != null))
            {
                return (localListener.Status == TaskStatus.Running) &&
                       (periodicSubscriber.Status == TaskStatus.Running);
            }
            else
                return false;
        }
    }

    public void Start()
    {
        keepWorking = true;
        localListener = new Task(() => LocalListenerWorker());
        localListener.Start();

        periodicSubscriber = new Task(() => PeriodicSubscriberWorker());
        periodicSubscriber.Start();
    }

    public void Stop()
    {
        keepWorking = false;
        localListener.Wait();
        periodicSubscriber.Wait();
    }

    async void LocalListenerWorker()
    {
        while (keepWorking)
        {
            // Do some work and then wait a bit
            await Task.Delay(1000);
        }
    }

    async void PeriodicSubscriberWorker()
    {
        while (keepWorking)
        {
            // Do some (other) work and then wait a bit
            await Task.Delay(1000);
        }
    }
}

为了测试这个样板,我使用了以下内容:

UdpClientConnector connector = new UdpClientConnector();

// This assert is successful because the two Tasks are not yet started
Assert.IsTrue(!connector.IsRunning);

// Starts the tasks and wait a bit
Connector.Start();
Task.Delay(2000).Wait();

// This fails 
Assert.IsTrue(connector.IsRunning);

当我尝试调试测试用例时,我发现两个任务处于RanToCompletion状态,这是意外的,因为这两个任务都只是循环而不应该终止keepWorking 1}}变得虚假。

我也尝试使用Task.Factory.StartNew(..)启动任务并获得相同的结果。

我缺少什么?谢谢!

1 个答案:

答案 0 :(得分:4)

问题在于如何启动任务,以及任务方法。

localListener = new Task(() => LocalListenerWorker());

LocalListenerWorker返回时,该任务将完成 - 它将立即执行 (当它遇到第一个await表达式时)。它不等待异步操作实际完成(即完成循环)。

async void方法几乎永远不会使用 - 它们基本上只用于支持事件处理程序。

我建议您重写方法以返回Task,然后使用Task.Run启动它们,传入方法组:

Task.Run(LocalListenerWorker);
...

private async Task LocalListenerWorker()
{
    // Body as before
}

Task.Run的任务只有在LocalListenerWorker返回的任务完成时才会完成,即循环体完成时。

这是一个完整的演示:

using System;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        Task task1 = Task.Run(Loop);
        // Don't do this normally! It's just as a simple demo
        // in a console app...
        task1.Wait();
        Console.WriteLine("First task done");

        Task task2 = new Task(() => Broken());
        task2.Start();
        // Don't do this normally! It's just as a simple demo
        // in a console app...
        task2.Wait();
        Console.WriteLine("Second task done");
    }

    static async Task Loop()
    {
        for (int i = 0; i < 5; i++)
        {
            await Task.Delay(1000);
            Console.WriteLine(i);
        }
    }

    static async void Broken()
    {
        for (int i = 0; i < 5; i++)
        {
            await Task.Delay(1000);
            Console.WriteLine(i);
        }
    }
}

输出显示:

0
1
2
3
4
First task done
Second task done

第一个任务按预期运行,仅在第一个异步方法确实完成时才完成。第二个任务的行为类似于您当前的代码:它在第二个异步方法返回后立即完成 - 由于await几乎立即发生。