Language: EN

ejecutar-codigo-bloqueante-de-terceros-con-timeout-en-c

Running Third-Party Blocking Code with Timeout in C#

I received a question through the Discord community on how to safely execute third-party code susceptible to blocking in C# using a Timeout to cancel the task.

Concurrency is always a tricky subject in practically every language. C# is not only no exception but is actually a good example. The reason is that over the years it has incorporated different functionalities but, logically, the previous ones have not been removed for compatibility reasons.

.NET is equipped with a large number of tools to manage concurrency, but at the same time, a large number of tutorials, code, and examples have become outdated by not using the latest tools.

As we were saying, a common example is having to deal with a task that is susceptible to getting blocked. For this, we usually want to use a timeout, that is, a time after which we cancel the task if it has not finished.

For this, we have different mechanisms. The most common one will be to use a CancellationToken. However, this requires that the task code being executed is designed to use this mechanism. However, in the case that we have to work with legacy code or third-party libraries, we may not have the ability to directly use a CancellationToken.

So let’s see one of the possible techniques to address this situation, in which we have to deal with code susceptible to getting blocked, which we cannot modify, and which we want to handle using a Timeout.

Let’s try with an example. First, we are going to create our “blocking code”. For this, we create a new library, and inside it, we simply put the following,

namespace ClassLibrary1
{
    public static class Testme
    {
        public static void DoMagic()
        {
            while (true)
            {
                Thread.Sleep(1000);
            }
        }
    }
}

That is, we have only created a method called ‘DoMagic’, which as we see uses a while(true) and a wait to permanently block the execution. We have put ourselves in the worst-case scenario, which is that the third-party code (in our case, simulated) does not use async, nor Task, nor any asynchronous mechanism. It is simply a blocking method, from the old days.

To test it, we create a new console application, and replace the code with the following,

  
Console.WriteLine("Starting");

ClassLibrary1.Testme.DoMagic();

Console.WriteLine("Finally");

We run the code and check that, as expected, ‘Starting’ is displayed but ‘Finally’ is never shown because the code is blocked in our (disastrous) simulated third-party library.

To conveniently resolve this, we create a class with extension methods for Tasks,

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    public static class TaskExtensions
    {
        public static async Task RunWithTimeout(this Task task, TimeSpan timeout)
        {

            using (var timeoutCancellationTokenSource = new CancellationTokenSource())
            {

                var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token));
                if (completedTask == task)
                {
                    timeoutCancellationTokenSource.Cancel();
                    return;
                }
                else
                {
                    throw new TimeoutException("The operation has timed out.");
                }
            }
        }

        public static async Task<TResult> RunWithTimeout<TResult>(this Task<TResult> task, TimeSpan timeout)
        {

            using (var timeoutCancellationTokenSource = new CancellationTokenSource())
            {

                var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token));
                if (completedTask == task)
                {
                    timeoutCancellationTokenSource.Cancel();
                    return await task;
                }
                else
                {
                    throw new TimeoutException("The operation has timed out.");
                }
            }
        }
    }
}

With this, we could execute our task with a Timeout by doing

using ConsoleApp1;

Console.WriteLine("Starting");

var task = Task.Factory.StartNew(() => { ClassLibrary1.Testme.DoMagic(); });

try
{
    await task.RunWithTimeout(TimeSpan.FromSeconds(3));
}
catch (TimeoutException ex)
{
    Console.WriteLine("Timeout");
}

Console.WriteLine("Finally");

If we run the code, we will see that “Starting” is displayed and, after 3 seconds, “Timeout” and “Finally”. That is, our Timeout is working correctly