【问题标题】:Mark task as completed将任务标记为已完成
【发布时间】:2014-07-19 18:10:14
【问题描述】:

我正在通过 TCP/IP 实现客户端协议 MyProtocol。协议的 Connect() 方法应该有一个类似于 TcpClient.ConnectAsync() 的签名——也就是说,它应该返回一个 Task:

Task MyProtocol.Connect (…);

这个方法(MyProtocol.Connect())应该建立异步 TCP/IP 连接(通过TcpClient.ConnectAsync()),返回一个未完成的任务 T,然后定期向服务器发送某个消息 M——再次异步(通过NetworkStream.WriteAsync() )。当从服务器接收到某个响应 R 时——再次异步(通过NetworkStream.ReadAsync()),MyProtocol.Connect() 应该完成任务 T。 我正在执行以下操作:

    // Client of the protocol:
var task = myProtocol.Connect(); // asynchronous call, we don’t want to wait until connected
task.ContinueWith(t =>
{
    // Connected – doing an OnConnected stuff
    …
});

// MyProtocol.Connect() implementation:
public class MyProtocol
{
    private Task connectTask;
    public Task Connect(…)
    {
        var tcpIpConnectTask = mTcpIpProtocol.Connect(…);
        tcpIpConnectTask.ContinueWith(t =>
        {
            connectTask = new Task();
        }
        return connectTask;
    }
}

显然,必须通过计时器定期向服务器发送消息 M。一旦从服务器异步接收到响应 R,connectTask 必须标记为已完成,我看不到这样做的方法。 好吧,严格来说,我已经成功地将connectTask 标记为已完成;我将它包装在TaskCompletionSource<bool> 中并使用TaskCompletionSource.SetResult(true)。 但是我想知道这是否是唯一的,更不用说完成我需要的最佳方式了?我特别不喜欢 TaskCompletionSource<> 必须有一个非空任务结果类型(我使用 bool),即没有非泛型版本。

在 TPL 到来之前,我们有自己的类似框架和方法Task.NotifyCompleted(),因此我们可以在一个地方创建一个任务,并在另一个地方将其标记为已完成——所有这些都是异步工作的。但我所读到的关于 TPL 中任务的所有内容似乎都暗示一个任务只有在其委托运行到最后一行时才能完成…… 还是我错过了一些简单的东西?

【问题讨论】:

  • 你为什么不直接返回tcpIpConnectTask?一旦连接完成,那一个就完成了。此外,如果您使用的是 .net 4.5,您可以使用async/await,这可以简化使用
  • tcpIpConnectTask 本质上是TcpClient.ConnectAsync() 返回的内容。如上所述,MyProtocol 为 TCP/IP 添加了自己的功能,所以它只能使用tcpIpConnectTask 作为中间结果,它不能将它返回给客户端,因为tcpIpConnectTask 时连接不会完成就 MyProtocol 而言,是完整的。

标签: c# asynchronous task-parallel-library task


【解决方案1】:

我尚未对此进行测试,但通过多个任务延续可能会实现您想要的效果,一旦您收到所需的响应,请将 connectedTask 设置为从最后一个 ReadAsync 收到的任务。

public class MyProtocol
{
    private Task connectTask;
    public Task Connect()
    {
        var tcpIpConnectTask = mTcpIpProtocol.Connect();
        tcpIpConnectTask.ContinueWith(t =>
        {
            bool finished = false;
            Task readAsyncTask = null;

            while(!finsihed)
            {              
                NetworkStream.WriteASync().
                ContinueWith(t1 =>
                {
                     if(t1.Exception == null)
                     {                  
                        readAsyncTask = NetworkStream.ReadASync().
                        ContinueWiht(t2 => 
                        {
                            if(t2.Exception == null)
                            {
                                if(t2.Result == /*Desired Response*/)
                                { 
                                    finished = true;
                                    connectedTask = t2;
                                }
                            }
                            else
                            {   
                                // Handle ReadAsync error
                            }
                        }, null, TaskScheduler.Default);
                    }
                    else
                    {
                       // handle WriteAsync error
                    }
                }, null, TaskScheduler.Default);    

                // Wait for the read operation to complete. This can cause a deadlock
                // when called from a UI thread as Wait() is a blocking call. Ensure we are waiting
                // on a background thread by specifying TaskScheduler.Default
                readAsyncTask.Wait();
            }
        }, null, TaskScheduler.Default);

        return connectTask;
}

或者,如果您希望任务具有特定结果,您可以返回 Task.FromResult(..),这也是一个已完成的任务。

【讨论】:

  • 谢谢,我也考虑过类似的选项,虽然还没有将它放入代码中,而且我不确定它是否会根据需要定期启动定时器。到目前为止TaskCompletionSource.GetResult() 看起来像是要走的路。而且我不能返回Task.FromResult,因为在返回的那一刻任务还没有完成。
【解决方案2】:

由于您已经在使用 .Net 4.5,因此您应该使用 C# 5.0 async-await,这正好适用于这种情况。代码可能看起来像(有点伪代码):

public Task ConnectAsync()
{
    await ClientConnectAsync();

    while (true)
    {
        await SendMessageAsync();

        var response = await ReceiveMessageAsync();

        if (response == R)
            return;

        await Task.Delay(period);
    }
}

真正回答你的问题:

但是我想知道这是否是唯一的,更不用说完成我需要的最佳方式了?

如果您不想使用async-await,那么可以,TaskCompletionSource 是唯一的通用方式。

我特别不喜欢 TaskCompletionSource<> 必须具有非 void 任务结果类型(我使用 bool),即没有非泛型版本。

这有点烦人,但这并不重要,因为 Task<T> 继承自 Task

但是我所读到的关于 TPL 中任务的所有内容似乎都暗示一个任务只有在其委托运行到最后一行时才能完成 [...]

对于确实有要运行的委托的Tasks 来说是这样(可以使用Task.Run()Task.Factory.StartNew()new Task() 创建)。但它不适用于没有委托的Tasks,可以使用async-awaitTaskCompletionSource 创建。

【讨论】:

  • 非常优雅的解决方案。不知何故,我没有想到我可以使用Task.Delay() 而不是计时器。我不确定await Task.Delay() 是否会像Thread.Sleep() 那样行事——这是我买不起的。我的意思是,在我的代码中,在该线程等待Task.Delay() 时,必须在该线程上异步执行其他部分。
  • @alexk await Task.Delay() 在等待时不会阻塞任何线程。这就是await 的重点,以避免阻塞线程。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-11-07
  • 2018-10-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-26
  • 1970-01-01
相关资源
最近更新 更多