【问题标题】:Task status changes to RanToCompletion if the Task await's something如果任务等待的东西,任务状态更改为 RanToCompletion
【发布时间】:2014-07-19 03:31:05
【问题描述】:

该问题描述了在此处找到的相同问题 - MSDN Developer Forum。该问题没有可接受的答案,给出的任何答案都不能应用于我的案例(因此是一个不同的问题)。

问题也源自我asked previously,但由于性质不同和更具体的问题,我要问一个新问题。

完整代码可以在这里找到: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

【问题讨论】:

  • 是否与您使用Task.Factory.StartNew 调用异步方法而不是让该方法返回Task 有关?

标签: c# .net asynchronous async-await .net-4.5


【解决方案1】:

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

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秒。

【讨论】:

  • 这样说不是更正确吗:不是Accept 方法立即返回,而是调用Accept 的任务完成。那是因为Accept 被定义为异步并且任务不需要等到它实际返回?
  • 好的,通过使Accept() 返回Task 而不是void 并在没有Task.Factory 的情况下开始确实使Task.WhenAll 等到完成。现在的问题是,Accept 的性质被破坏了——它(TcpListener)一次不响应多个连接——线程丢失(如预期的那样)。
  • @jolt:里面有很多代码,我不完全确定你想要什么。您能否将您的新问题缩小到更易于管理的范围内并将其作为一个新问题提出?
  • @FarhadAlizadehNoori 似乎是个好点子。由于它返回 Task,因此包装器任务在内部任务启动后立即返回。使用 Task.Run() 将解决这个问题。
  • @FarhadAlizadehNoori: 不。如果您查看编译Accept 方法时生成的代码,该方法本身只是构建了一个带有一些任务延续的状态机,然后将控制权返回给调用方法在这些延续执行之前。调用Accept() 的代码无法知道Accept 是否标记为async:如果删除async 关键字,公共签名不会发生任何变化:只有方法本身的行为会发生变化。
【解决方案2】:

有两个问题:async voidTask.Factory.StartNew。这两种做法都是不好的做法。

首先,async void does not allow the calling code to know when it completes。因此,您等待并不重要; async void 方法将很快返回,并且您的应用程序无法知道 Accept 何时实际完成。要解决此问题,请将async void 替换为更合适的async Task

第二,StartNew doesn't understand asynchronous delegatesStartNew 是一个极其底层的 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);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-06-18
    • 1970-01-01
    • 2013-12-11
    • 2011-02-05
    • 2013-08-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多