Search code examples
c#asynchronoustimeoutinterop

Timeout for asynchronous Task<T> with additional exception handling


In my project, I reference types and interfaces from a dynamic link library. The very first thing I have to do when using this specific library is to create an instance of EA.Repository, which is defined within the library and serves as kind of an entry point for further usage.

The instantiation EA.Repository repository = new EA.Repository() performs some complex stuff in the background, and I find myself confronted with three possible outcomes:

  1. Instantiation takes some time but finishes successfully in the end
  2. An exception is thrown (either immediately or after some time)
  3. The instantiation blocks forever (in which case I'd like to cancel and inform the user)

I was able to come up with an asynchronous approach using Task:

public static void Connect()
{
    // Do the lengthy instantiation asynchronously
    Task<EA.Repository> task = Task.Run(() => { return new EA.Repository(); });

    bool isCompletedInTime;

    try
    {
        // Timeout after 5.0 seconds
        isCompletedInTime = task.Wait(5000);
    }
    catch (Exception)
    {
        // If the instantiation fails (in time), throw a custom exception
        throw new ConnectionException();
    }

    if (isCompletedInTime)
    {
        // If the instantiation finishes in time, store the object for later
        EapManager.Repository = task.Result;
    }
    else
    {       
        // If the instantiation did not finish in time, throw a custom exception
        throw new TimeoutException();
    }
}

(I know, you can probably already spot a lot of issues here. Please be patient with me... Recommendations would be appreciated!)

This approach works so far - I can simulate both the "exception" and the "timeout" scenario and I obtain the desired behavior.

However, I have identified another edge case: Let's assume the instantiation task takes long enough that the timeout expires and then throws an exception. In this case, I sometimes end up with an AggregateException saying that the task has not been observed.

I'm struggling to find a feasible solution to this. I can't really cancel the task when the timeout expires, because the blocking instantiation obviously prevents me from using the CancellationToken approach.

The only thing I could come up with is to start observing the task asynchronously (i.e. start another task) right before throwing my custom TimeoutException:

Task observerTask = Task.Run(() => {
    try { task.Wait(); }
    catch (Exception) { }
});

throw new TimeoutException();

Of course, if the instantiation really blocks forever, I already had the first task never finish. With the observer task, now I even have two!

I'm quite insecure about this whole approach, so any advice would be welcome!

Thank you very much in advance!


Solution

  • Here is an extension method that you could use to explicitly observe the tasks that may fail while unobserved:

    public static Task<T> AsObserved<T>(this Task<T> task)
    {
        task.ContinueWith(t => t.Exception);
        return task;
    }
    

    Usage example:

    var task = Task.Run(() => new EA.Repository()).AsObserved();