net - sqlcommand commandtimeout c#
Implementar C#Timeout genérico (7)
¿Qué hay de usar 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");
}
Estoy buscando buenas ideas para implementar una forma genérica de tener una sola línea (o delegado anónimo) de código ejecutado con un tiempo de espera.
TemperamentalClass tc = new TemperamentalClass();
tc.DoSomething(); // normally runs in 30 sec. Want to error at 1 min
Estoy buscando una solución que pueda implementarse elegantemente en muchos lugares donde mi código interactúa con el código temperamental (que no puedo cambiar).
Además, me gustaría que el código "tiempo de espera excedido" se detenga para poder ejecutarlo aún más si es posible.
Acabo de noquear esto ahora, así que podría necesitar alguna mejora, pero haré lo que quieras. Es una aplicación de consola simple, pero demuestra los principios necesarios.
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.");
}
}
}
Algunos pequeños cambios a la gran respuesta de Pop Catalin:
- Func en lugar de Acción
- Lanzar excepción en el valor de tiempo de espera malo
- Llamar a EndInvoke en caso de tiempo de espera
Se han agregado sobrecargas para ayudar al trabajador de señalización a cancelar la ejecución:
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
}
Así es como lo haría:
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!");
}
}
Bien, podría hacer cosas con los delegados (BeginInvoke, con una devolución de llamada configurando un indicador, y el código original esperando ese indicador o tiempo de espera), pero el problema es que es muy difícil cerrar el código en ejecución. Por ejemplo, matar (o pausar) un hilo es peligroso ... así que no creo que haya una manera fácil de hacerlo de forma robusta.
Voy a publicar esto, pero tenga en cuenta que no es ideal, no detiene la tarea de larga ejecución y no se limpia correctamente en caso de falla.
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();
}
}
La parte realmente difícil fue matar la tarea de larga ejecución al pasar el hilo ejecutor de la Acción a un lugar donde podría abortarse. Logré esto con el uso de un delegado envuelto que pasa el hilo para matar en una variable local en el método que creó el lambda.
Presento este ejemplo para su disfrute. El método que realmente te interesa es CallWithTimeout. Esto cancelará el hilo de larga ejecución al abortarlo, y al tragar la ThreadAbortException :
Uso:
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);
}
El método estático haciendo el trabajo:
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();
}
}
}
Estamos utilizando un código como este en gran medida en la producción :
var result = WaitFor<Result>.Run(1.Minutes(), () => service.GetSomeFragileResult());
La implementación es de código abierto, funciona eficientemente incluso en escenarios informáticos paralelos y está disponible como parte de las bibliotecas compartidas de 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);
}
}
Este código todavía tiene errores, puedes probar con este pequeño programa de prueba:
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());
}
}
Hay una condición de carrera. Es claramente posible que se genere una ThreadAbortException después de que se WaitFor<int>.Run()
al método WaitFor<int>.Run()
. No encontré una manera confiable de solucionar esto, sin embargo, con la misma prueba no puedo reproducir ningún problema con la respuesta aceptada por TheSoftwareJedi .