通过线程和委托来设置函数的执行超时(方案二已经测试通过)

方案一:
down vote
accepted
Here is a generic solution that allows you to wrap any method in a timeout:

http://kossovsky.net/index.php/2009/07/csharp-how-to-limit-method-execution-time/

It uses the useful Thread.Join overload that accepts a timeout in milliseconds rather than manually using timers. The only thing
I would do differently is swap the success flag and result value to match the TryParse pattern, as follows:

public static T Execute<T>(Func<T> func, int timeout)
{
T result;
TryExecute(func, timeout, out result);
return result;
}

public static bool TryExecute<T>(Func<T> func, int timeout, out T result)
{
var t = default(T);
var thread = new Thread(() => t = func());
thread.Start();
var completed = thread.Join(timeout);
if (!completed) thread.Abort();
result = t;
return completed;
}
And this is how you would use it:

var func = new Func<string>(() =>
{
Thread.Sleep(200);
return "success";
});
string result;
Debug.Assert(!TryExecute(func, 100, out result));
Debug.Assert(result == null);
Debug.Assert(TryExecute(func, 300, out result));
Debug.Assert(result == "success");
You could also add overloads that accept Action instead of Func if you want to execute a method that doesn't return a value.


方案二:
The really tricky part here was killing the long running task through passing the executor thread from the Action back to a place
where it could be aborted. I accomplished this with the use of a wrapped delegate that passes out the thread to kill into a local
variable in the method that created the lambda.

I submit this example, for your enjoyment. The method you are really interested in is CallWithTimeout. This will cancel the long
running thread by aborting it, and swallowing the ThreadAbortException:

Usage:

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);
}
The static method doing the work:

static void CallWithTimeout(Action action, int timeoutMilliseconds)
{
Thread threadToKill = null;
Action wrappedAction = () =>
{
threadToKill = Thread.CurrentThread;
action();
};

IAsyncResult result = wrappedAction.BeginInvoke(null, null);
if (result.AsyncWaitHandle.WaitOne(timeoutMilliseconds))
{
wrappedAction.EndInvoke(result);
}
else
{
threadToKill.Abort();
throw new TimeoutException();
}
}

}

方案二本人测试已经通过

private IList<ADGroupNormalData> GrabAdgroupsNormal(string strHtmlAdgroup)
        {
            //抓取表格
            string strRegexRule = @"<table width=""100%"" class=""tb3"" id=""pio"">.*?</table>";
            Regex regex = new Regex(strRegexRule, RegexOptions.Singleline | RegexOptions.Multiline | RegexOptions.IgnoreCase);
            string strTable = regex.Match(strHtmlAdgroup).ToString();

            //抓取推广组
            string strRegexRule2 = @"<tr.*?>" +
                //@".*?<a href='(?<detailUrl>http://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?)' target=""_blank"">" +
                //@".*?<img id=""imgUrl_(?<adgroupId>\d+)"" src='(?<itemPicUrl>http://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?)'.*?/>.*?</a>" +
                @".*?<a href='(?<detailUrl>.*?)' target=""_blank"">" +
                @".*?<img id=""imgUrl_(?<adgroupId>\d+)"" src='(?<itemPicUrl>.*?)'.*?/>.*?</a>" +
                @".*?<div id='adGroupTitle_(?:\d+)'>(?<adGroupTitle>.*?)</div>" +
                @".*?<p class=""co"">(?<itemPrice>.*?)</p>" +
                @".*?<td id='onlineState_(?:\d+)'.*?>(?<onlineState>.*?)</td>" +
                @".*?<a href=""javascript:showModDefaultPriceTip(.*?)"">(?<defaultPrice>.*?)</a>" +
                @".*?</tr>";

            Regex regex2 = new Regex(strRegexRule2, RegexOptions.Singleline | RegexOptions.Multiline | RegexOptions.IgnoreCase);
            MatchCollection matchs = regex2.Matches(strTable);
            IList<ADGroupNormalData> mADGroupNormalDataLst = CallWithTimeout(MatchToList, matchs, 100000);
            return mADGroupNormalDataLst;
        }

        #region 通过线程和委托来设置函数的执行超时
        private delegate IList<ADGroupNormalData> OnMatchToListDelegate(MatchCollection matchs);
        static IList<ADGroupNormalData> MatchToList(MatchCollection matchs)
        {
            IList<ADGroupNormalData> mADGroupNormalDataLst = new List<ADGroupNormalData>();
            foreach (Match m in matchs)
            {
                string strDetailUrl = m.Groups["detailUrl"].ToString();
                string strAdgroupId = m.Groups["adgroupId"].ToString();
                string strItemPicUrl = m.Groups["itemPicUrl"].ToString();
                string strAdGroupTitle = m.Groups["adGroupTitle"].ToString();
                string strItemPrice = m.Groups["itemPrice"].ToString();
                strItemPrice = strItemPrice.Contains("") ? strItemPrice.Substring(1, strItemPrice.Length - 1) : strItemPrice;
                string strOnlineState = m.Groups["onlineState"].ToString().Trim();
                string strDefaultPrice = m.Groups["defaultPrice"].ToString();
                strDefaultPrice = strDefaultPrice.Contains("") ? strDefaultPrice.Substring(1, strDefaultPrice.Length - 1) : strDefaultPrice;
                string[] strArray = strDetailUrl.Split(new string[] { "=" }, StringSplitOptions.None);
                long numId = strArray.Length > 1 ? long.Parse(strArray[1].ToString()) : 0;

                if (strOnlineState.Contains("<"))
                {
                    int idx = strOnlineState.IndexOf("<");
                    strOnlineState = strOnlineState.Substring(0, idx);
                    strOnlineState = strOnlineState.Replace("\r", "");
                    strOnlineState = strOnlineState.Replace("\n", "");
                    strOnlineState = strOnlineState.Replace("\t", "");
                }

                //if (strOnlineState.Contains("排查下架"))
                //    strOnlineState = "排查下架";
                //else if (strOnlineState.Contains("淘宝下架"))
                //    strOnlineState = "淘宝下架";
                //else if (strOnlineState.Contains("屏蔽中"))
                //    strOnlineState = "屏蔽中";

                ADGroupNormalData mADGroupNormalData = new ADGroupNormalData();
                mADGroupNormalData.adgroupId = long.Parse(strAdgroupId);
                mADGroupNormalData.adGroupTitle = strAdGroupTitle;
                mADGroupNormalData.detailUrl = strDetailUrl;
                mADGroupNormalData.onlineState = strOnlineState.Trim();
                mADGroupNormalData.defaultPrice = decimal.Parse(strDefaultPrice);
                mADGroupNormalData.itemPrice = decimal.Parse(strItemPrice);
                mADGroupNormalData.itemPicUrl = strItemPicUrl;
                mADGroupNormalData.numId = numId;
                mADGroupNormalDataLst.Add(mADGroupNormalData);
            }
            return mADGroupNormalDataLst;
        }
        static IList<ADGroupNormalData> CallWithTimeout(OnMatchToListDelegate method, MatchCollection matchs, int timeoutMilliseconds)
        {
            Thread threadToKill = null;

            /*
             * 使用匿名函数创建委托(委托于函数的参数类型,个数和返回值类型必须一致)
             * (MatchCollection mcs) => 表示该匿名函数的传入参数
             * return method(mcs); 表示该匿名函数的返回类型
             */
            OnMatchToListDelegate matchMethod = (MatchCollection mcs) =>
            {
                threadToKill = Thread.CurrentThread;
                return method(mcs);
            };

            /*
             * BeginInvoke 通过委托异步调用目标函数,并传入相应的参数
             * 阻塞当前线程timeoutMilliseconds秒,直到当前等待句柄收到信号(如:异步操作完成时生成的信号)
             * 如果等待句柄在指定时间间隔后没有收到异步操作完成的信号,则表示异步操作过程中可能出现异常,
             * 导致委托调用超时,调用 threadToKill.Abort() 终止当前线程。
             * 否则,委托正常结束 matchMethod.EndInvoke(result).
             */
            IAsyncResult result = matchMethod.BeginInvoke(matchs, null, null);
            if (result.AsyncWaitHandle.WaitOne(timeoutMilliseconds))
            {
                return matchMethod.EndInvoke(result);
            }
            else
            {
                threadToKill.Abort();
                throw new MethodExecuteTimeoutException("函数在调用过程中出现未知错误,导致函数调用超时异常");
            }
        }
        #endregion

 

方案三:
*We are using code like this heavily in productio*n:

var result = WaitFor<Result>.Run(1.Minutes(), () => service.GetSomeFragileResult());
Implementation is open-sourced, works efficiently even in parallel computing scenarios and is available as a part of Lokad Shared
Libraries

/// <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);
}
}
share|improve this answer
edited Apr 15 '11 at 8:45

answered Jun 13 '09 at 11:54

Rinat Abdullin
10.8k13261
2
This what I implemented, It can handle parameters and return value, which I prefer and needed. Thanks Rinat – Gabriel Mongeon Jul
12 '10 at 20:11
4
what is [Immutable]? – raklos Jan 29 '11 at 13:26
1
Just an attribute we use to mark immutable classes (immutability is verified by Mono Cecil in unit tests) – Rinat Abdullin Feb 1
'11 at 16:47
5
This is a deadlock waiting to happen (I’m surprised you haven’t observed it yet). Your call to watchedThread.Abort() is inside a
lock, which also needs to be acquired in the finally block. This means while the finally block is waiting for the lock (because
the watchedThread has it between Wait() returning and Thread.Abort()), the watchedThread.Abort() call will also block indefinitely
waiting for the finally to finish (which it never will). Therad.Abort() can block if a protected region of code is running -
causing deadlocks, see - msdn.microsoft.com/en-us/library/ty8d3wta.aspx – trickdev Apr 12 '11 at 10:10
1
Upd: fixed the post with the corrected code. – Rinat Abdullin Apr 15 '11 at 8:47

方案四:
Well, you could do things with delegates (BeginInvoke, with a callback setting a flag - and the original code waiting for that
flag or timeout) - but the problem is that it is very hard to shut down the running code. For example, killing (or pausing) a
thread is dangerous... so I don't think there is an easy way to do this robustly.

I'll post this, but note it is not ideal - it doesn't stop the long-running task, and it doesn't clean up properly on failure.

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();
}
}
share|improve this answer
answered Nov 18 '08 at 16:06

Marc Gravell♦
360k458301314
1
I'm perfectly happy killing something that's gone rouge on me. Its still better than letting it eat CPU cycles until the next
reboot (this is part of a windows service). – chilltemp Nov 18 '08 at 16:09
2
In that case, consider spawning an AppDomain that you can kill... – Marc Gravell♦ Nov 18 '08 at 16:11
2
result.AsyncWaitHandle can be used, no manual reset needed – TheSoftwareJedi Nov 18 '08 at 16:57
@Marc : I am great fan of yours. But, this time, I wonder, why u did not use result.AsyncWaitHandle as mentioned by
TheSoftwareJedi. Any benefit of using ManualResetEvent over AsyncWaitHandle? – Anand Patel Dec 19 '11 at 17:30
1
@Anand well, this was some years ago so I can't answer from memory - but "easy to understand" counts for a lot in threaded code –
Marc Gravell♦ Dec 19 '11 at 18:28

方案五:
Some minor changes to Pop Catalin's great answer:

Func instead of Action
Throw exception on bad timeout value
Calling EndInvoke in case of timeout
Overloads have been added to support signaling worker to cancel execution:

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
}
share|improve this answer
edited May 15 '09 at 19:16

answered May 15 '09 at 15:11

George Tsiokos
768816
any sample code call Invoke method ? – Kiquenet Mar 28 '11 at 13:48
Invoke(e => { // ... if (error) e.Cancel = true; return 5; }, TimeSpan.FromSeconds(5)); – George Tsiokos Apr 4 '11 at 21:07
It's worth pointing out that in this answer the 'timed out' method is left running unless it can be modified to politely choose to
exit when flagged with 'cancel'. – David Eison Sep 30 '11 at 4:42
David, that's what the CancellationToken type (.NET 4.0) was specifically created to address. In this answer, I used
CancelEventArgs so the worker could poll args.Cancel to see if it should exit, although this should be re-implemented with the
CancellationToken for .NET 4.0. – George Tsiokos Dec 21 '11 at 15:55
A usage note on this that confused me for a little while: You need two try/catch blocks if your Function/Action code may throw an
exception after timeout. You need one try/catch around the call to Invoke to catch TimeoutException. You need a second inside your
Function/Action to capture and swallow/log any exception that may occur after your timeout throws. Otherwise the app will
terminate with an unhandled exception (my use case is ping testing a WCF connection on a tighter timeout than specified in
app.config) – fiat Oct 19 at 0:20
show 1 more comment
feedback


方案六:

6
down vote
I just knocked this out now so it might need some improvement, but will do what you want. It is a simple console app, but
demonstrates the principles needed.

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.");
}
}

}
share|improve this answer
answered Nov 18 '08 at 16:13

Jason Jackson
9,50712351
Nice. The only thing I would add is that he might prefer to throw System.TimeoutException rather than just System.Exception –
Joel Coehoorn Nov 18 '08 at 16:30
Oh, yeah: and I'd wrap that in it's own class as well. – Joel Coehoorn Nov 18 '08 at 16:32
result.AsyncWaitHandle can be used, no manual reset needed. – TheSoftwareJedi Nov 18 '08 at 16:35

 

posted @ 2012-12-21 15:39  xust  阅读(471)  评论(0)    收藏  举报