简体   繁体   中英

Cancel execution of command if it takes too long

I am currently working on a piece of software that uses an assembly from a different department.
I call a Method from this assembly like this:

using (var connection = (IConnection) Factory.GetObject(typeof (IConnection)))

The code used to work perfectly fine. But for the last few minutes it seemed like my program was doing nothing when I tried to launch it. Pressing pause while debugging showed me it got "stuck" at the line above.
My guess is they're just doing some maintenance or something but that's not the point here.
So I though it would be nice to tell the user what went wrong if the program doesn't start. Something simple like

MessageBox.Show("Could not connect", "Connection Error");

And then close the program. My question is:
How do I terminate the execution of a command after a set amount of time and jump somewhere else?
My guess would be moving it to a separate thread and then putting the calling thread to sleep for a few seconds after which it disposes of the extra thread if it isn't completed yet. But that seems really dirty to me and there's got to be a better way.

Your question can be separated into two parts:

  1. How to terminate the execution of a command?

The only way is to abort the thread. But don't do it . There is no guaranteed and safe way. There are such methods like Thread.Interrupt and Thread.Abort that can wake up the thread. But they will work only if the thread is in the WaitSleepJoin state and it hangs in managed code.

Seems like you already know it. But once again, if something inside the assembly hangs infinitely the execution of code then the thread is probably "gone". So you are right that the program should be closed.

  1. ... jump somewhere else?

Good approach is using of TPL and async model. Here is an extension method to wrap up any Task and expires after timeout.

public static async Task TimeoutAfter(this Task task, int millisecondsTimeout)
{
    if (task == await Task.WhenAny(task, Task.Delay(millisecondsTimeout)))
        await task;
    else
        throw new TimeoutException();
}

Then use it

try
{
    using (var result = await Task.Run(() => (IConnection)Factory.GetObject(typeof(IConnection))).TimeoutAfter(1000))
    {
        ...
    }
}
catch (TimeoutException ex)
{
    //timeout
}

Here you can find more information

A simple way of doing it without extra libraries or extension methods:

using ( var task = new Task<IConnection>( () => Factory.GetObject( typeof( IConnection ) ) ) )
{
    task.Start();

    if( !task.Wait( timeoutMilliseconds ) )
    {
        throw new TimeoutException();
    }

    IConnection result = task.Result;
}

Task.Wait does what you want, because you can throw an exception if it returns false (task didn't complete in time.)

It's even simpler if you have an Action that doesn't return something:

if ( !Task.Run( action ).Wait( timeoutMilliseconds ) )
{
    throw new TimeoutException();
}

Where action is some Action or lambda.

The easiest way to do this, if a native timeout is not implemented, as you mentioned, is a separate thread to load it on. Although this sounds like it'll be really dirty, it's as simple as (using Rx):

Task<IConnection> connectionTask = Observable.Start(() => Factory.GetObject(typeof (IConnection)), Scheduler.Default).Timeout(TimeSpan.FromSeconds(20)).ToTask());
using (var connection = connectionTask.Result)
{
    ...
}

You can tweak the Scheduler if you don't want it to run on the threadpool. It will throw a TimeoutException if the Factory.GetObject call takes longer than 20 seconds.

You could use CancellationTokenSource to set a timeout on the operation

var timeoutCts = new CancellationTokenSource();
try
{
    timeoutCts.CancelAfter(300000); // Cancel after 5 minutes
    // ... run your long term operation
}
catch (OperationCanceledException ex)
{
    // Handle the timeout
}

Please see this documentation from Microsoft

using System;
using System.Threading.Tasks;

public class Example
{
    public static void Main()
    {
        Task t = Task.Run(() =>
        {
            Random rnd = new Random();
            long sum = 0;
            int n = 5000000;
            for (int ctr = 1; ctr <= n; ctr++)
            {
                int number = rnd.Next(0, 101);
                sum += number;
            }
            Console.WriteLine("Total:   {0:N0}", sum);
            Console.WriteLine("Mean:    {0:N2}", sum / n);
            Console.WriteLine("N:       {0:N0}", n);
        });
        TimeSpan ts = TimeSpan.FromMilliseconds(150);
        if (!t.Wait(ts))
            Console.WriteLine("The timeout interval elapsed.");
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM