如何将时基轮询与等待任务结合起来

How to combine time base polling with awaitable Task

我已经实现了一个基于 Timer 的轮询工作者。例如,您可以在客户端考虑 TryConnect——我调用 TryConnect,它最终会在一段时间后连接。它处理多个线程,如果连接在进程中已经所有后续 TryConnect returns 立即无需任何额外操作。在内部,我只是创建了一个计时器,并每隔一段时间尝试连接——如果连接失败,我会再试一次。等等。

小缺点是它是 "fire&forget" 模式,现在我想将它与 "async/await" 模式结合起来,即调用:

client.TryConnect(); // returns immediately
// cannot tell if I am connected at this point

我想这样称呼它:

await client.TryConnect();
// I am connected for sure

如何更改我的实现以支持 "async/await"?我在考虑创建空Task(只是为了await),然后用FromResult完成它,但是这个方法创建一个新任务,它没有完成给定的实例。

为了记录,当前的实现看起来像这样(只是代码的草图):

public void TryConnect()
{
   if (this.timer!=null)
   {
     this.timer = new Timer(_ => tryConnect(),null,-1,-1);
     this.timer.Change(0,-1); 
   }
}
private void tryConnect()
{
   if (/*connection failed*/)
     this.timer.Change(interval,-1);
   else
     this.timer = null;
}

缺乏好的Minimal, Complete, and Verifiable code example不可能提供任何具体的建议。考虑到您所写的内容,可能 您正在寻找的是 TaskCompletionSource。例如:

private TaskCompletionSource<bool> _tcs;

public async Task TryConnect()
{
   if (/* no connection exists */)
   {
     if (_tcs == null)
     {
       this.timer = new Timer(_ => tryConnect(),null,-1,-1);
       this.timer.Change(0,-1); 
       _tcs = new TaskCompletionSource<bool>();
     }

     await _tcs.Task;
   }
}

private void tryConnect()
{
   if (/*connection failed*/)
     this.timer.Change(interval,-1);
   else
   {
     _tcs.SetResult(true);
     _tcs = null;
     this.timer = null;
   }
}

备注:

  • 如果在建立连接后再次调用 TryConnect(),您的原始代码示例将重试连接逻辑。我希望您真正想要的是也检查是否存在有效连接,因此我稍微修改了上面的内容以进行检查。如果你真的总是想尝试一个新的连接,你当然可以删除那部分,即使一个已经存在。
  • 代码在设置结果后立即将 _tcs 设置为 null。请注意,任何等待或以其他方式存储 _tcs 对象的 Task 值的代码都将隐式引用当前 _tcs 对象,因此在这里丢弃该字段的引用不是问题。
  • 没有非泛型 TaskCompletionSource。因此,对于只需要 Task 的场景,您可以将泛型类型与占位符类型一起使用,例如我在此处所做的 boolobject 或其他。我可以调用 SetResult(false)SetResult(true) 一样好,在这个例子中这无关紧要。重要的是 Task 完成,而不是返回什么值。
  • 上面使用 async 关键字使 TryConnect() 成为异步方法。恕我直言,这更具可读性,但当然会在额外的 Task 中产生轻微的开销来表示方法的操作。如果你愿意,你可以不用 async 方法直接做同样的事情:
public Task TryConnect()
{
   if (/* no connection exists */)
   {
     if (_tcs == null)
     {
       this.timer = new Timer(_ => tryConnect(),null,-1,-1);
       this.timer.Change(0,-1); 
       _tcs = new TaskCompletionSource<bool>();
     }

     return _tcs.Task;
   }

   return Task.CompletedTask;
}