如果任务等待's,则任务状态更改为RanToCompletion

本文关键字:任务 状态 RanToCompletion 等待 如果 | 更新日期: 2023-09-27 18:05:41

该问题描述了在MSDN开发者论坛中发现的相同问题。这个问题没有一个公认的答案,给出的任何答案都不能适用于我的情况(因此是另一个问题)。

这个问题也是我之前问过的一个问题的衍生,但是由于性质不同,问题也更具体,所以我要问一个新的问题。

完整的代码可以在这里找到:http://pastebin.com/uhBGWC5e
*唯一改变的是任务完成检查(while -> Task.WhenAll)。


等待任务内部异步操作时,任务状态变为RanToCompletion,尽管任务仍在运行。

现在,让我们看看设置:

// Start async.
Task t1 = Task.Factory.StartNew(Accept, s1);
Task t2 = Task.Factory.StartNew(Accept, s1);
Task.WhenAll(t1, t2).Wait();

Accept法:

public static async void Accept(object state)
{
    TcpListenerEx server = (TcpListenerEx) state;
    IPEndPoint endPoint = server.LocalEndpoint as IPEndPoint;
    Log("Accepting clients on {0}", endPoint);
    while (true)
    {
        var client = server.AcceptTcpClientAsync();
        if (client == null)
        {
            Log("Null error on accept");
            break;
        }
        TcpClient connected = (TcpClient) client;
        servers[server].Add(connected);
        bool stop = await Task<Task<bool>>.Factory.StartNew(Listen, connected).Unwrap();
        if (stop == true)
        {
            break;
        }
    }
    // Stop the server.
    server.Stop();
    Log("Stoppped {0}", endPoint);
}

由于TaskStatus改变为RanToCompletion, Task.WhenAll().Wait()调用标记自己完成得相当快,导致程序被进一步执行,最终被终止。

但是,从理论上讲,Accept任务不应该停止,它一直在监听连接,直到显式停止。

这里的问题是什么,导致任务被标记为RanToCompletion过早?

如果任务等待's,则任务状态更改为RanToCompletion

我可以用更少的代码重现这个问题:

void Main()
{
    Task t1 = Task.Factory.StartNew(Accept);
    t1.Wait();
    Console.WriteLine("Main ended");
}
public static async void Accept()
{
    while (true)
    {
        await Task.Delay(1000);
    }
    Console.WriteLine("Stoppped");
}

但这是正确的:

void Main()
{
    Task t1 = Accept();
    t1.Wait();
    Console.WriteLine("Main ended");
}
public static async Task Accept()
{
    while (true)
    {
        await Task.Delay(1000);
    }
    Console.WriteLine("Stoppped");
}

基本上,通过使用Task.Factory.StartNew(),您正在创建一个基于派生的单独线程的Task来调用给定的委托(Accept()方法)。Accept方法本身(像任何好的async方法一样)实际上立即返回。因此,调用它的线程立即完成了它的任务,因此创建用来表示该线程的Task也立即完成。

如果你允许Accept()返回一个Task而不是void,那么它返回的Task就是你应该等待的,如果你想等到它运行完所有的await

有两个错误:async voidTask.Factory.StartNew。这两个都是不好的做法。

首先,async void不允许调用代码知道它何时完成。所以你在等待并不重要;async void方法会很快返回,你的应用程序不知道Accept什么时候真正结束。要解决这个问题,将async void替换为更合适的async Task

第二,StartNew不理解异步委托。StartNew是一个非常低级的API,不应该在99.99%的产品代码中使用。用Task.Run代替。

public static async Task Accept(object state);
Task t1 = Task.Run(() => Accept(s1));
Task t2 = Task.Run(() => Accept(s1));
Task.WaitAll(t1, t2);

我也遇到过类似的问题。

我有一个阅读器任务,我想触发读取数据并处理它。因此我使用:

var readTrigger = new ManualResetEventSlim(); 

为了在需要时优雅地停止线程,我使用了CancellationToken。因为我不希望我的任务持续运行和轮询数据(而是使用事件来通知新数据),所以我使用了一个阻塞函数来在无事可做时将任务置于睡眠状态。因此,我必须侦听两个事件,这是使用WaitHandle完成的。

var action = WaitHandle.WaitAny(new WaitHandle[] { readTrigger.WaitHandle, cancellationToken.WaitHandle });

可能有一个不同的,更好的解决方案与WaitHandle与async/await(如果有人知道请告诉我:))

该函数仍然包含异步函数,尽管我必须等待。这导致了一个异步线程函数,它具有以下函数头:

private async Task ReadDataTask(CancellationToken cancellationToken)

WaitHandle函数阻塞等待,直到至少一个事件被触发。由于我正在使用WaitHandle,我需要使用一个单独的线程来防止阻塞我的整个应用程序。我使用下面的调用在一个单独的线程中启动我的Task:

var t = new Task(async() => await ReadDataTask(ct), ct, TaskCreationOptions.LongRunning);

我遇到了同样的问题,任务tReadDataTask函数返回之前终止。因为我必须使用WaitHandle(或者让我们说想要),所以前面的解决方案都不适合我。

我的问题的解决方法相当简单。我没有使用await来等待我的ReadDataTask,而是使用了来自返回任务的wait,这会导致以下调用:

var t = new Task(() => ReadDataTask(ct).Wait(), ct, TaskCreationOptions.LongRunning);

我希望我能帮你解决这个问题。

亲切的问候