3

我有一些异步方法:

// 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<T>通用的,如果我有 100 种方法,我将不得不为每个返回类型创建一个变量。但是由于一次只能调用一个方法,因此最好使用单个方法TaskCompletionSource,但不要将其键入到object (TaskCompletionSource<object>).

我不想这样做:

object a = await service.GetAAsync();

因为这需要客户进行铸造。所以最好的解决方案是有一个单一的TaskCompletionSource,但以某种方式输入。或者可以有一个字典TaskCompletionSource。这两者在我看来都是不可能的。

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

更新:

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

4

2 回答 2

4

我不想这样做,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;
}
于 2019-03-29T09:55:22.390 回答
0

这可以通过TaskCompletionSourceDictionary <TKey>轻松完成:

目标:

  • 任何类型都可以用作键 - 我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。但移除可能很危险 - 为最后一个消费者移除。

于 2020-07-03T16:55:12.793 回答