2

I have an asynchronous method that uses a limited resource. If the resource becomes unavailable, I want to delay future calls until the resource becomes available again.

Basically, if an exception occurs while accessing the limited resource, a single thread will handle the error by delaying all threads that also caught the exception, and other threads that call the method. After 5 seconds the thread will retry accessing the resource. It's kind of like throttling.

I've implemented this by abusing TaskCompletionSource combined with a SemaphoreSlim. It appears to work. Can this be improved to be less... hacky?

// Use SemaphoreSlim to make sure only one thread handles an error at a time.
private static readonly SemaphoreSlim mySemaphore = new SemaphoreSlim(1);

// Use TaskCompletionSource as a flag to delay threads while an error is handled.
private static volatile TaskCompletionSource<bool> myFlag;

static MyClass()
{
    myFlag = new TaskCompletionSource<bool>();
    myFlag.SetResult(false); // At startup there is no error being handled.
}

public async Task DoSomethingAsync()
{
    while (true)
    {
        await myFlag.Task; // Wait if an error is being handled.

        try
        {
            await ... // Call some asynchronous operations here that can cause errors.
            return;
        }
        catch
        {
            await mySemaphore.WaitAsync(); // Wait so only one thread handles an error.
            bool wasHandled = await myFlag.Task; // Wait and check if error was handled.

            if (wasHandled == false)
            {
                // Reset TaskCompletionSource so error handling on other threads waits.
                myFlag = new TaskCompletionSource<bool>();
                mySemaphore.Release();

                await Task.Delay(5000); // "Handle" the error by waiting 5 seconds.
                myFlag.SetResult(true); // Notify waiting threads an error was handled.

                // Reset TaskCompletionSource 
                myFlag = new TaskCompletionSource<bool>();
                myFlag.SetResult(false);
            }
            else // (wasHandled == true)
            {
                mySemaphore.Release(); // Move along, nothing to see here.
            }
        }
    }
}

To clarify why I think this should be improved: I am using TaskCompletionSource to create an awaitable boolean state, and to reset it I must instantiate a new TaskCompletionSource every time. I don't think this is an intended use for TaskCompletionSource.

I have looked at ManualResetEvent and AutoResetEvent as they seem to do what I need, but they don't offer asynchronous functionality.

VMAtm
  • 27,943
  • 17
  • 79
  • 125
Rudey
  • 4,717
  • 4
  • 42
  • 84
  • 2
    This is a terrible idea – hyankov Feb 11 '17 at 18:11
  • 1
    @HristoYankov That's why I'm posting here. Care to propose a better idea? – Rudey Feb 11 '17 at 18:15
  • 2
    There are libraries for this, like Polly (https://github.com/App-vNext/Polly). Seems like their Circuit Breaker implementation can be useful for your scenario. – Peter Bons Feb 11 '17 at 20:05
  • @PeterBons I was unable to achieve my goal with Polly, [I opened an issue on GitHub](https://github.com/App-vNext/Polly/issues/230). Great library though! – Rudey Feb 12 '17 at 00:12

2 Answers2

2

Some ideas for you here, with code samples. However, you really should investigate the Circuit Breaker pattern, so you could implement it by yourself or get the working implementation.

As you already now, reset event implementations could work for you, as the AutoResetEvent cover the case for the handling the exception one by one (as it allows only one thread to run through it), and ManualResetEventSlim cover the case for running all the threads in case of normal execution:

var manual = new ManualResetEventSlim(true);
var auto = new AutoResetEvent(true);
while (true)
{
    // check for normal work
    manual.Wait();
    try
    {
    }
    catch
    {
        auto.Wait();
        // only one thread here
        // stop all the worker threads
        manual.Reset();
        // handling here

        // start all the workers
        manual.Set();
        // start exception handlers
        auto.Set();
    }
}

Note that you can simulate the slim version for auto event with SemaforSlim (as you already did). You should use SemaforSlim(1, 1) constructor, for exactly 1 thread to be able to proceed, and the semafor is initially set.

Still, this version is non-async, so you have some choice here.

  1. Semafor methods could be awaited, and there is a AsyncManualResetEvent implementation from @StephenCleary.
  2. Also, you can use some retry logic with timeout parameters for waiting loops. In this case, you can either yield the execution time with Thread.Yield method or, if you want to set a retry timeout, simply use the Delay task from TPL:

    while (!manual.Wait(SMALL_TIMEOUT_FOR_WAITING)
    {
        // Thread.Yield();
        await Task.Delay(LARGE_RETRY_TIMEOUT);
    }
    

You should also note that it's a common practice to introduce some CancellationToken for your loops and task-oriented methods so your code became more managed in case of stopping the whole system.

Community
  • 1
  • 1
VMAtm
  • 27,943
  • 17
  • 79
  • 125
  • Thanks for mentioning Stephen Cleary's AsyncEx library. I'll try that tomorrow. – Rudey Feb 12 '17 at 00:41
  • How can I use `AutoResetEvent` to make sure only one thread does the handling? In your example, handling will occur one thread at a time. I don't wait the waiting threads to do handling after the first thread has completed. – Rudey Feb 12 '17 at 11:57
  • Just do the check with a Wait(timeout) - if a thread didn't enter the exception handling for a small amount of time it means that other one had, so you can simply `continue;` the loop – VMAtm Feb 12 '17 at 15:37
  • I just realized I can still use `SemaphoreSlim` instead of `AutoResetEvent`. It's `WaitAsync` method has an overload that accepts a timeout and returns a boolean, I can use that to determine what thread should do the handling. Stephen Cleary's AsyncEx library has been a big help so far, it's `AsyncManualResetEvent` is really helpful. – Rudey Feb 12 '17 at 23:39
  • Yeah, this what I've meant. See the linked question about the simulation the AutoResetEvent. Good luck with your projects:) – VMAtm Feb 12 '17 at 23:49
0

If I understand your situation correctly: Consumer --> Your service --> Another service/resource.

You shouldn't implement a back-off strategy in your service, because the end-client is waiting on the other end of the line. Let the client implement the back-off, your service just needs to implement a circuit-breaker and even that's optional.

So...

On the Service side:

You should have a private worker method (e.g. DoSomething) which only take care of 'Doing Something'. It should be wrapped in a public method, which implements Circuit Breaker pattern.

On the 'Consumer' side:

Consumers of your 'service' (the public method) should implement Exponential Backoff Strategy.

hyankov
  • 4,049
  • 1
  • 29
  • 46
  • The page for "Exponential Backoff Strategy" states: "This content and the technology described is outdated and is no longer being maintained.". Are you sure I should use that? – Rudey Feb 11 '17 at 18:32
  • The application block I have linked to might be outdated. I should have linked to an abstract description of the exponential backoff strategy pattern instead. It is still valid. – hyankov Feb 11 '17 at 18:37
  • I understand I need separation of concerns. But *how* do I implement these patterns using C#'s `async`-`await`, though? The page you provided for "Exponential Backoff Strategy" ends up using `Thread.Sleep` in it's example, which is what I need to avoid. – Rudey Feb 11 '17 at 18:49