Search code examples
c#genericscastingasync-awaittaskcompletionsource

How to form collection of TaskCompletionSource<T> and keep type safety


I have some async methods:

// 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;
}

The above is semi-pseudo code for simplicity. I call one of the methods like so:

A a = await service.GetAAsync();

Now the problem is the TaskCompletionSource<T> is generic, if I have a 100 methods in this way I would have to create a variable for each return type. But since only one method can be called at once it would be nice to use a single TaskCompletionSource, yet not type it to object (TaskCompletionSource<object>).

I don't want to do:

object a = await service.GetAAsync();

Because that will need casting by the client. So the best solution would be to have a single TaskCompletionSource, but have it typed somehow. Or alternatively be able to have a Dictionary of TaskCompletionSource. Both of which seem impossible to me.

How should I solve this?

Update:

For a background on my situation have a look at: Wrap synchronous code into async await in disconnected scenario


Solution

  • I don't want to do object a = await service.GetAAsync() because that will need casting by the client.

    Not necessarily. Keep the same signature, but add a call to ContinueWith to cast to the appropriate type:

    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);
    }
    

    Or you can do the same thing using 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;
    }