标记任务已完成

3

我正在实现一个基于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<> 必须具有非 void 任务结果类型(我使用 bool),即没有非泛型版本。 在 TPL 出现之前,我们有自己类似的框架和方法 Task.NotifyCompleted(),因此我们可以在一个地方创建任务,在另一个地方标记任务已完成 - 所有这些都是异步完成的。但是我所读到的关于 TPL 中任务的所有内容似乎都意味着只有当任务的委托运行到最后一行时,任务才会完成…… 或者我错过了什么简单的东西吗?

为什么不直接返回 tcpIpConnectTask 呢?一旦连接完成,它就已经完成了。此外,如果您使用的是 .net 4.5,您可以使用 async/await 来简化使用。 - NeddySpaghetti
tcpIpConnectTask 本质上就是 TcpClient.ConnectAsync() 的返回值。根据上文所述,MyProtocol 在 TCP/IP 上添加了自己的功能,因此它只能将 tcpIpConnectTask 作为中间结果使用,不能将其返回给客户端,因为在 MyProtocol 看来,当 tcpIpConnectTask 完成时连接并不完整。 - alexk
2个回答

3

既然你已经在使用.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中任务的所有内容似乎都暗示任务只有在其委托运行到最后一行时才会完成[...]

对于有委托需要运行的Task来说是正确的(可以使用Task.Run()Task.Factory.StartNew()new Task()创建它们)。 但是对于没有委托需要运行的Task,可以使用async-awaitTaskCompletionSource创建。


非常优雅的解决方案。我不知道我可以使用Task.Delay()而不是计时器。但我不确定await Task.Delay()是否会像Thread.Sleep()一样 - 这是我无法承受的。我的意思是,在我的代码中,当它在Task.Delay()等待时,必须异步执行此线程上的其他部分。 - alexk
@alexk await Task.Delay() 在等待时不会阻塞任何线程。这就是 await 的全部意义,避免阻塞线程。 - svick

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,因为在返回任务时,任务尚未完成。 - alexk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接