【问题标题】:How to form collection of TaskCompletionSource<T> and keep type safety如何形成 TaskCompletionSource<T> 的集合并保持类型安全
【发布时间】:2019-03-29 09:46:31
【问题描述】:

我有一些异步方法:

// not ideal
private TaskCompletionSource<A> _tcsA;
private TaskCompletionSource<A> _tcsB;
private TaskCompletionSource<A> _tcsC;
...

public Task<A> GetAAsync() {
  _currentTask = TaskType.A;
  _tcsA = new TaskCompletionSource<A>();
  // some complex non-sync task, using http/events that ends with Complete();
  // QueueRequest?.Invoke(this, taskInfo); // raise request -- this does not return
  return _tcsA.Task;
}

public Task<B> GetBAsync() {
  _currentTask = TaskType.B;
  _tcsB = new TaskCompletionSource<B>();
  // some complex non-sync task, using http/events that ends with Complete();
  // QueueRequest?.Invoke(this, taskInfo); // raise request -- this does not return
  return _tcsB.Task;
}

public Task<C> GetCAsync() {
  _currentTask = TaskType.C;
  _tcsC = new TaskCompletionSource<C>();
  // some complex non-sync task, using http/events that ends with Complete();
  // QueueRequest?.Invoke(this, taskInfo); // raise request -- this does not return
  return _tcsC.Task;
}

// called by an external source, a http listener / processor
// this should complete the async call by the client and return results
public void Complete(Result result) {
  switch (_currentTask) {
    case TaskType.A:
      _tcsA.SetResult(new A());
      break;
    case TaskType.B:
      _tcsB.SetResult(new B());
      break;
    case TaskType.C:
      _tcsC.SetResult(new C());
      break;
  }

  _currentTask = TaskType.None;
}

为简单起见,以上是半伪代码。我这样称呼其中一种方法:

A a = await service.GetAAsync();

现在的问题是 TaskCompletionSource&lt;T&gt; 是通用的,如果我有 100 个这样的方法,我将不得不为每个返回类型创建一个变量。但由于一次只能调用一个方法,最好使用单个TaskCompletionSource,但不要将其键入object (TaskCompletionSource&lt;object&gt;)

我不想做:

object a = await service.GetAAsync();

因为这需要客户进行强制转换。所以最好的解决方案是有一个TaskCompletionSource,但要以某种方式输入。或者可以拥有TaskCompletionSource 的字典。这两个对我来说似乎都是不可能的。

我应该如何解决这个问题?

更新:

有关我的情况的背景,请查看:Wrap synchronous code into async await in disconnected scenario

【问题讨论】:

  • 看看generic type parameters他们可能会帮助你
  • 没有任何非通用 API 用于此,因此您拥有的最佳选择确实是使用 object 用于 TCS。对不起。
  • 看看这个thread 和批准的答案
  • @PavelAnikhouski 这不是一回事;在 那个 的情况下,result 是非泛型的 (Task)。在 this 情况下,输入结果 - Task&lt;A&gt;Task&lt;B&gt; 等,问题是 OP 只需要 TCS 的一个字段(因为它不希望处理多个并发操作),但 TCS 本质上是类型化/通用的
  • @MarcGravell 或者...我也很高兴能够将 TCS 对象添加到集合中,同时保持类型安全。我知道这是一个类似的问题。

标签: c# generics casting async-await taskcompletionsource


【解决方案1】:

我不想做object a = await service.GetAAsync(),因为那需要客户强制转换。

不一定。保持相同的签名,但添加对 ContinueWith 的调用以转换为适当的类型:

public Task<A> GetAAsync() {
  _currentTask = TaskType.A;
  _tcs = new TaskCompletionSource<object>();
  // some complex task that ends with Complete();
  return _tcs.Task.ContinueWith(t => (A)t.Result);
}

或者你可以使用 async/await 来做同样的事情:

public async Task<A> GetAAsync() {
  _currentTask = TaskType.A;
  _tcs = new TaskCompletionSource<object>();
  // some complex task that ends with Complete();
  return (A)await _tcs.Task;
}

【讨论】:

  • 哇,这可能是这个问题最干净的解决方案!我会试一试并标记为已回答。
  • @sprocket12 一个更清洁的解决方案是根本不使用 TCS。在该方法之外,TCS 没有任何意义,这意味着它不应存储在字段中。您可以使用 Task.FromResult() 代替 tcs
  • @PanagiotisKanavos Task.FromResult 只有在some complex task that ends with Complete() 同步时才可行。也就是说,我同意 tcs 不应存储在字段中,而是传递给异步工作流
  • @sprocket12 这可能看起来更干净,但效率要低得多;通过这样做,您实际上创建了两倍于您需要的任务
  • 第一个答案以危险的方式使用ContinueWith。从语义的角度来看,使用async/await 会更好。
【解决方案2】:

这很容易通过TaskCompletionSourceDictionary &lt;TKey&gt; 完成:

目标:

  • 任何类型都可以用作密钥 - 我在此示例中使用了您的 TaskType 枚举
  • 如果每种类型只有一个实例,则有更简单的非泛型版本
  • 生产者和消费者的动态数量
  • 结果的完成可以被多个消费者观察到
  • 强制执行类型安全 - 生产者和消费者必须指定相同的类型,否则强制转换异常描述类型差异
  • 生产者可以在消费者请求任务之前设置结果
  • 消费者可以在生产者设置结果之前请求任务

我们可以通过这个新类的GetOrAdd 方法完成所有这些:

TaskCompletionSource<TValue> GetOrAdd<TValue>(TKey key)

示例用法:

private readonly TaskCompletionSourceDictionary<TaskType> _tasks = new TaskCompletionSourceDictionary<TaskType>();

public Task<A> GetAAsync() =>
    _tasks.GetOrAdd<A>(TaskType.A).Task;

public Task<B> GetBAsync() =>
    _tasks.GetOrAdd<B>(TaskType.B).Task;

public Task<C> GetCAsync() =>
    _tasks.GetOrAdd<C>(TaskType.C).Task;

// called by an external source, a http listener / processor
// this should complete the async call by the client and return results
public void Complete(Result result)
{
    switch (_currentTask)
    {
        case TaskType.A:
            _tasks.GetOrAdd<A>(TaskType.A).TrySetResult(new A());
            break;
        case TaskType.B:
            _tasks.GetOrAdd<B>(TaskType.B).TrySetResult(new B());
            break;
        case TaskType.C:
            _tasks.GetOrAdd<C>(TaskType.C).TrySetResult(new C());
            break;
    }

    _currentTask = TaskType.None;
}

除了GetOrAdd,还有TryAddTryRemove。但移除可能很危险 - 为最后一个消费者移除。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-18
    • 1970-01-01
    • 2011-01-13
    • 1970-01-01
    • 2023-03-25
    • 1970-01-01
    相关资源
    最近更新 更多