Установить тайм-аут на операцию - PullRequest
40 голосов
/ 15 февраля 2010

У меня есть объект obj, являющийся сторонним компонентом,

// this could take more than 30 seconds
int result = obj.PerformInitTransaction(); 

Я не знаю, что происходит внутри. Что я знаю, так это то, что если это займет больше времени, это не удастся.

как настроить механизм тайм-аута для этой операции, чтобы, если это занимает более 30 секунд, я просто выбрасывал MoreThan30SecondsException?

Ответы [ 10 ]

72 голосов
/ 15 февраля 2010

Вы можете запустить операцию в отдельном потоке и затем установить таймаут для операции объединения потоков:

using System.Threading;

class Program {
    static void DoSomething() {
        try {
            // your call here...
            obj.PerformInitTransaction();         
        } catch (ThreadAbortException) {
            // cleanup code, if needed...
        }
    }

    public static void Main(params string[] args) {

        Thread t = new Thread(DoSomething);
        t.Start();
        if (!t.Join(TimeSpan.FromSeconds(30))) {
            t.Abort();
            throw new Exception("More than 30 secs.");
        }
    }
}
12 голосов
/ 13 апреля 2016

Проще, используя Task.Wait(TimeSpan):

using System.Threading.Tasks;

var task = Task.Run(() => obj.PerformInitTransaction());
if (task.Wait(TimeSpan.FromSeconds(30)))
    return task.Result;
else
    throw new Exception("Timed out");
6 голосов
/ 15 февраля 2010

Если вы не хотите блокировать основной поток, вы можете использовать System.Threading.Timer :

private Thread _thread;

void Main(string[] args)
{
    _thread = new ThreadStart(ThreadEntry);
    _thread.Start();
    Timer timer = new Timer(Timeout,null,30000,Timeout.Infinite);
}


void ThreadEntry()
{
    int result = obj.PerformInitTransaction(); 
}

void TimeOut(object state)
{
    // Abort the thread - see the comments
    _thread.Abort();

    throw new ItTimedOutException();
}

У Джона Скита есть менее сильный способ ( отключение рабочих нитей изящно ) остановки нити, чем прерывание.

Однако, поскольку вы не контролируете операции, которые выполняет PerformInitTransaction(), вы мало что можете сделать, если Abort завершается неудачно и оставляет объект в недопустимом состоянии. Как уже упоминалось, если вам удастся очистить все, что прервало PerformInitTransaction, то вы можете сделать это, перехватив ThreadAbortException, хотя, поскольку это вызов третьей стороны, это будет означать угадывание состояния, в котором вы оставили свой метод в.

PerformInitTransaction действительно должен быть тем, который предоставляет тайм-аут.

2 голосов
/ 27 марта 2018

Ниже приведены две реализации, которые также выдают любое исключение, которое происходит во внутренней задаче.

Для действий (без возвращаемого значения):

public static bool DoWithTimeout(Action action, int timeout)
{
    Exception ex = null;
    CancellationTokenSource cts = new CancellationTokenSource();
    Task task = Task.Run(() =>
    {
        try
        {
            using (cts.Token.Register(Thread.CurrentThread.Abort))
            {
                action();
            }
        }
        catch (Exception e)
        {
            if (!(e is ThreadAbortException))
                ex = e;
        }
    }, cts.Token);
    bool done = task.Wait(timeout);
    if (ex != null)
        throw ex;
    if (!done)
        cts.Cancel();
    return done;
}

Для функций (с возвращаемым значением):

public static bool DoWithTimeout<T>(Func<T> func, int timeout, out T result)
{
    Exception ex = null;
    result = default(T);
    T res = default(T);
    CancellationTokenSource cts = new CancellationTokenSource();
    Task task = Task.Run(() =>
    {
        try
        {
            using (cts.Token.Register(Thread.CurrentThread.Abort))
            {
                res = func();
            }
        }
        catch (Exception e)
        {
            if (!(e is ThreadAbortException))
                ex = e;
        }
    }, cts.Token);
    bool done = task.Wait(timeout);
    if (ex != null)
        throw ex;
    if (done)
        result = res;
    else
        cts.Cancel();
    return done;
}
2 голосов
/ 15 февраля 2010

Вы должны быть осторожны при прерывании операции, подобной этой, тем более что в стороннем компоненте у вас (возможно) нет доступа к коду для изменения.

Если вы прервете операцию, вы не будете знать, в каком состоянии вы оставили базовый класс. Например, он, возможно, получил блокировку, и ваша команда заставила эту блокировку не сниматься. Даже если вы уничтожите объект после прерывания операции, он может изменить какое-то глобальное для него состояние, и поэтому вы не сможете надежно создать новый экземпляр без перезапуска.

1 голос
/ 15 февраля 2010

Вы можете посмотреть на вызов метода в потоке и по истечении времени ожидания прервать поток и вызвать исключение. Кроме того, в этом случае вам придется обработать исключение ThreadBorted.

0 голосов
/ 08 октября 2018

Я только что столкнулся с этим в приложении .NET 4.0 (нет доступа к Task.Run, Task.Delay и т. Д.). Если вы извините последнюю строку (которая является частью setTimeout), она довольно лаконична.

int sleepTime = 10000;
Action myAction = () => {
    // my awesome cross-thread update code    
    this.BackColor = Color.Red;
};
new System.Threading.Thread(() => { System.Threading.Thread.Sleep(sleepTime); if (InvokeRequired) myAction(); else myAction(); }).Start();
0 голосов
/ 17 апреля 2018

Я думаю, что это проще всего:

using System.Threading.Tasks;

var timeToWait = 30000; //ms
Task.Run(async () =>
{
    await Task.Delay(timeToWait);
    //do your timed task i.e. --
    int result = obj.PerformInitTransaction(); 
});
0 голосов
/ 14 марта 2018

это то, что я бы использовал. работает аналогично тому, как работает тайм-аут JavaScript.

public class Toolz {
    public static System.Threading.Tasks.Task<object> SetTimeout(Func<object> func, int secs) {
        System.Threading.Thread.Sleep(TimeSpan.FromSeconds(secs));
        return System.Threading.Tasks.Task.Run(() => func());
    }
}

class Program {
    static void Main(string[] args) {
        Console.WriteLine(DateTime.Now);
        Toolz.SetTimeout(() => {
            Console.WriteLine(DateTime.Now);
            return "";
        }, 10);
    }

}
0 голосов
/ 15 февраля 2010

Есть хороший пример общего решения для этого с использованием вспомогательного класса здесь .

Он использует делегат Action, чтобы избежать создания / уничтожения потока, показанного в предыдущем примере.

Надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...