-2

I'm stumped on how to implement a cancellation / retry mechanism for the following code, it is mostly pseudo-code for brevity:

class CLI {
    async void Main() {
        var response = await bot.GetSomething(); // final usage
    }
}

class Bot {
    private void StartTask() {
      _taskCompSource = new TaskCompletionSource<object>();
    }

    private async Task<T> ResultPromise<T>() {
      return await _taskCompSource.Task.ContinueWith(t => t.Result != null ? (T)t.Result : default(T));
    }

    // send request
    public Task<string> GetSomething() {
        StartTask();
        QueueRequest?.Invoke(this, new Request(...)); // complex non-sync request, fired off here
        return ResultPromise<string>();
    }

    // receive response
    public void Process(Reply reply) {
        // process reply
        _taskCompSource.SetResult("success");
    }
}

class BotManager {
    // bot.QueueRequest += QueueRequest;
    // _service.ReplyReceived += ReplyReceived;

    private void QueueRequest(object sender, Request request) {
      _service.QueueRequestForSending(request);
    }

    private async void ReplyReceived(object sender, Reply reply) {
      GetBot().Process(reply);
    }
}

class Service {
    private Dictionary<string, Queue<Request>> _queues;

    // send receive loop
    private async void HttpCallback(IAsyncResult result) {
       while (true) {
        // if reply received then call ReplyReceived?.Invoke(this, reply);
        // check if request already in progress // ##ISSUE IS HERE##
        // if not send another request
        // keep connection to server open by feeding spaces
       }
    }

    public void QueueRequestForSending(Request request) {
      GetQueueForBot().Enqueue(request);
    }
}

I would like to implement a timeout on await bot.GetSomething(); but not sure how to do it with this kind of disconnected nature. I tried:

static class Extensions {
    private static async Task<T> RunTaskWithRetry<T>(Func<Task<T>> taskFunc, int retries, int timeout) {
      do {
        var task = taskFunc(); // just adds another request to queue which never gets ran because the previous one is waited forever to return, this needs to cancel the previous request, from here, but how?

        await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false);
        if (task.Status == TaskStatus.RanToCompletion) {
          return task.Result;
        }

        retries--;
      }
      while (retries > 0);

      return default(T);
    }

    public async static Task<T> WithRetry<T>(this Task<T> task, int retries = 3, int timeout = 10000) {
      return await RunTaskWithRetry(async () => await task, retries, timeout);
    }
}

Which can be called like await bot.GetSomething().WithRetry(); but the issue here is it just adds another request to the queue and not cancel or remove the existing one. To cancel, I would simply have to remove the existing request from the awaiting list. Problem is I do not know how to do it all the way from where the extension method is.

I would like to know any possible mechanism I can use to achieve timeout and cancellation.

sprocket12
  • 5,368
  • 18
  • 64
  • 133

2 Answers2

0

I do not know how to do it all the way from where the extension method is.

The code that controls the TaskCompletionSource<T> must also control its cancellation. That code can be implemented using TaskCompletionSource<T>.TrySetCanceled. If you also need to cancel the StartTask operation, then you would probably want to model that using CancellationToken.Register. I have an async wait queue that is similar except it manages a queue of TaskCompletionSource<T> instances rather than just one.

On a side note, the retry logic is iffy. The result of WhenAny is the task that completes, so checking Status isn't necessary; and Result should be replaced with await to avoid AggregateException wrappers:

var completed = await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false);
if (task == completed) {
  return await task;
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
-3

You can use Task instead await for this propose

  Task responseTask = bot.GetSomething(); // final usage
  responseTask.Wait(5000); //Timeout in 5 seconds.
  var result = responseTask.Result;

If you want to use more complex system like Cancellation Policies, read this post that maybe helps you: https://johnthiriet.com/cancel-asynchronous-operation-in-csharp/

Kifreak
  • 1
  • 1