한 줄 (또는 익명 대리자)의 코드를 시간 초과로 실행하는 일반적인 방법을 구현하는 좋은 아이디어를 찾고 있습니다.
TemperamentalClass tc = new TemperamentalClass();
tc.DoSomething(); // normally runs in 30 sec. Want to error at 1 min
내 코드가 변덕스러운 코드와 상호 작용하는 여러 곳에서 우아하게 구현할 수있는 솔루션을 찾고 있습니다 (변경할 수 없음).
또한 가능한 경우 문제가되는 “시간 초과”코드가 더 이상 실행되지 않도록하고 싶습니다.
답변
여기서 정말 까다로운 부분은 실행자 스레드를 Action에서 중단 될 수있는 위치로 다시 전달하여 장기 실행 작업을 죽이는 것입니다. 람다를 만든 메서드에서 로컬 변수로 죽이기 위해 스레드를 전달하는 래핑 된 대리자를 사용하여이 작업을 수행했습니다.
즐거움을 위해이 예를 제출합니다. 정말로 관심이있는 메서드는 CallWithTimeout입니다. 이것은 그것을 중단하고 ThreadAbortException을 삼킴으로써 장기 실행 스레드를 취소합니다. .
용법:
class Program
{
static void Main(string[] args)
{
//try the five second method with a 6 second timeout
CallWithTimeout(FiveSecondMethod, 6000);
//try the five second method with a 4 second timeout
//this will throw a timeout exception
CallWithTimeout(FiveSecondMethod, 4000);
}
static void FiveSecondMethod()
{
Thread.Sleep(5000);
}
작업을 수행하는 정적 메서드 :
static void CallWithTimeout(Action action, int timeoutMilliseconds)
{
Thread threadToKill = null;
Action wrappedAction = () =>
{
threadToKill = Thread.CurrentThread;
try
{
action();
}
catch(ThreadAbortException ex){
Thread.ResetAbort();// cancel hard aborting, lets to finish it nicely.
}
};
IAsyncResult result = wrappedAction.BeginInvoke(null, null);
if (result.AsyncWaitHandle.WaitOne(timeoutMilliseconds))
{
wrappedAction.EndInvoke(result);
}
else
{
threadToKill.Abort();
throw new TimeoutException();
}
}
}
답변
우리는 생산에서 다음과 같은 코드를 많이 사용하고 있습니다 .
var result = WaitFor<Result>.Run(1.Minutes(), () => service.GetSomeFragileResult());
구현은 오픈 소스이며 병렬 컴퓨팅 시나리오에서도 효율적으로 작동하며 Lokad 공유 라이브러리 의 일부로 제공됩니다.
/// <summary>
/// Helper class for invoking tasks with timeout. Overhead is 0,005 ms.
/// </summary>
/// <typeparam name="TResult">The type of the result.</typeparam>
[Immutable]
public sealed class WaitFor<TResult>
{
readonly TimeSpan _timeout;
/// <summary>
/// Initializes a new instance of the <see cref="WaitFor{T}"/> class,
/// using the specified timeout for all operations.
/// </summary>
/// <param name="timeout">The timeout.</param>
public WaitFor(TimeSpan timeout)
{
_timeout = timeout;
}
/// <summary>
/// Executes the spcified function within the current thread, aborting it
/// if it does not complete within the specified timeout interval.
/// </summary>
/// <param name="function">The function.</param>
/// <returns>result of the function</returns>
/// <remarks>
/// The performance trick is that we do not interrupt the current
/// running thread. Instead, we just create a watcher that will sleep
/// until the originating thread terminates or until the timeout is
/// elapsed.
/// </remarks>
/// <exception cref="ArgumentNullException">if function is null</exception>
/// <exception cref="TimeoutException">if the function does not finish in time </exception>
public TResult Run(Func<TResult> function)
{
if (function == null) throw new ArgumentNullException("function");
var sync = new object();
var isCompleted = false;
WaitCallback watcher = obj =>
{
var watchedThread = obj as Thread;
lock (sync)
{
if (!isCompleted)
{
Monitor.Wait(sync, _timeout);
}
}
// CAUTION: the call to Abort() can be blocking in rare situations
// http://msdn.microsoft.com/en-us/library/ty8d3wta.aspx
// Hence, it should not be called with the 'lock' as it could deadlock
// with the 'finally' block below.
if (!isCompleted)
{
watchedThread.Abort();
}
};
try
{
ThreadPool.QueueUserWorkItem(watcher, Thread.CurrentThread);
return function();
}
catch (ThreadAbortException)
{
// This is our own exception.
Thread.ResetAbort();
throw new TimeoutException(string.Format("The operation has timed out after {0}.", _timeout));
}
finally
{
lock (sync)
{
isCompleted = true;
Monitor.Pulse(sync);
}
}
}
/// <summary>
/// Executes the spcified function within the current thread, aborting it
/// if it does not complete within the specified timeout interval.
/// </summary>
/// <param name="timeout">The timeout.</param>
/// <param name="function">The function.</param>
/// <returns>result of the function</returns>
/// <remarks>
/// The performance trick is that we do not interrupt the current
/// running thread. Instead, we just create a watcher that will sleep
/// until the originating thread terminates or until the timeout is
/// elapsed.
/// </remarks>
/// <exception cref="ArgumentNullException">if function is null</exception>
/// <exception cref="TimeoutException">if the function does not finish in time </exception>
public static TResult Run(TimeSpan timeout, Func<TResult> function)
{
return new WaitFor<TResult>(timeout).Run(function);
}
}
이 코드는 여전히 버그가 있습니다.이 작은 테스트 프로그램으로 시도해 볼 수 있습니다.
static void Main(string[] args) {
// Use a sb instead of Console.WriteLine() that is modifying how synchronous object are working
var sb = new StringBuilder();
for (var j = 1; j < 10; j++) // do the experiment 10 times to have chances to see the ThreadAbortException
for (var ii = 8; ii < 15; ii++) {
int i = ii;
try {
Debug.WriteLine(i);
try {
WaitFor<int>.Run(TimeSpan.FromMilliseconds(10), () => {
Thread.Sleep(i);
sb.Append("Processed " + i + "\r\n");
return i;
});
}
catch (TimeoutException) {
sb.Append("Time out for " + i + "\r\n");
}
Thread.Sleep(10); // Here to wait until we get the abort procedure
}
catch (ThreadAbortException) {
Thread.ResetAbort();
sb.Append(" *** ThreadAbortException on " + i + " *** \r\n");
}
}
Console.WriteLine(sb.ToString());
}
}
경쟁 조건이 있습니다. 메서드 WaitFor<int>.Run()
가 호출 된 후에 ThreadAbortException이 발생할 가능성이 분명합니다 . 나는 이것을 고칠 믿을만한 방법을 찾지 못했지만 동일한 테스트로 TheSoftwareJedi가 받아 들인 대답으로 문제를 재현 할 수 없습니다 .
답변
글쎄, 당신은 델리게이트로 일을 할 수 있습니다 (플래그를 설정하는 콜백과 그 플래그 또는 타임 아웃을 기다리는 원래 코드)-문제는 실행중인 코드를 종료하기가 매우 어렵다는 것입니다. 예를 들어 스레드를 죽이는 (또는 일시 중지) 위험합니다 … 그래서 이것을 강력하게 수행하는 쉬운 방법이 없다고 생각합니다.
나는 이것을 게시 할 것이지만 이상적이지 않다는 점에 유의하십시오. 장기 실행 작업을 중지하지 않으며 실패시 제대로 정리되지 않습니다.
static void Main()
{
DoWork(OK, 5000);
DoWork(Nasty, 5000);
}
static void OK()
{
Thread.Sleep(1000);
}
static void Nasty()
{
Thread.Sleep(10000);
}
static void DoWork(Action action, int timeout)
{
ManualResetEvent evt = new ManualResetEvent(false);
AsyncCallback cb = delegate {evt.Set();};
IAsyncResult result = action.BeginInvoke(cb, null);
if (evt.WaitOne(timeout))
{
action.EndInvoke(result);
}
else
{
throw new TimeoutException();
}
}
static T DoWork<T>(Func<T> func, int timeout)
{
ManualResetEvent evt = new ManualResetEvent(false);
AsyncCallback cb = delegate { evt.Set(); };
IAsyncResult result = func.BeginInvoke(cb, null);
if (evt.WaitOne(timeout))
{
return func.EndInvoke(result);
}
else
{
throw new TimeoutException();
}
}
답변
Pop Catalin의 훌륭한 답변에 대한 몇 가지 사소한 변경 사항 :
- Action 대신 Func
- 잘못된 시간 초과 값에 예외 발생
- 시간 초과시 EndInvoke 호출
작업자에게 실행 취소를 알리는 신호를 지원하기 위해 오버로드가 추가되었습니다.
public static T Invoke<T> (Func<CancelEventArgs, T> function, TimeSpan timeout) {
if (timeout.TotalMilliseconds <= 0)
throw new ArgumentOutOfRangeException ("timeout");
CancelEventArgs args = new CancelEventArgs (false);
IAsyncResult functionResult = function.BeginInvoke (args, null, null);
WaitHandle waitHandle = functionResult.AsyncWaitHandle;
if (!waitHandle.WaitOne (timeout)) {
args.Cancel = true; // flag to worker that it should cancel!
/* •————————————————————————————————————————————————————————————————————————•
| IMPORTANT: Always call EndInvoke to complete your asynchronous call. |
| http://msdn.microsoft.com/en-us/library/2e08f6yc(VS.80).aspx |
| (even though we arn't interested in the result) |
•————————————————————————————————————————————————————————————————————————• */
ThreadPool.UnsafeRegisterWaitForSingleObject (waitHandle,
(state, timedOut) => function.EndInvoke (functionResult),
null, -1, true);
throw new TimeoutException ();
}
else
return function.EndInvoke (functionResult);
}
public static T Invoke<T> (Func<T> function, TimeSpan timeout) {
return Invoke (args => function (), timeout); // ignore CancelEventArgs
}
public static void Invoke (Action<CancelEventArgs> action, TimeSpan timeout) {
Invoke<int> (args => { // pass a function that returns 0 & ignore result
action (args);
return 0;
}, timeout);
}
public static void TryInvoke (Action action, TimeSpan timeout) {
Invoke (args => action (), timeout); // ignore CancelEventArgs
}
답변
이것이 내가 할 방법입니다.
public static class Runner
{
public static void Run(Action action, TimeSpan timeout)
{
IAsyncResult ar = action.BeginInvoke(null, null);
if (ar.AsyncWaitHandle.WaitOne(timeout))
action.EndInvoke(ar); // This is necesary so that any exceptions thrown by action delegate is rethrown on completion
else
throw new TimeoutException("Action failed to complete using the given timeout!");
}
}
답변
개선이 필요할 수 있도록 지금이 문제를 해결했지만 원하는대로 수행하겠습니다. 간단한 콘솔 앱이지만 필요한 원칙을 보여줍니다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace TemporalThingy
{
class Program
{
static void Main(string[] args)
{
Action action = () => Thread.Sleep(10000);
DoSomething(action, 5000);
Console.ReadKey();
}
static void DoSomething(Action action, int timeout)
{
EventWaitHandle waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
AsyncCallback callback = ar => waitHandle.Set();
action.BeginInvoke(callback, null);
if (!waitHandle.WaitOne(timeout))
throw new Exception("Failed to complete in the timeout specified.");
}
}
}
답변
Thread.Join (int timeout)을 사용하는 것은 어떻습니까?
public static void CallWithTimeout(Action act, int millisecondsTimeout)
{
var thread = new Thread(new ThreadStart(act));
thread.Start();
if (!thread.Join(millisecondsTimeout))
throw new Exception("Timed out");
}