将操作的超时时间设置为...

49

我有一个对象obj,它是第三方组件。

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

我不知道里面发生了什么。 我知道的是如果它花费更长的时间,它就会失败。

如何设置一个超时机制来控制这个操作,如果它花费的时间超过30秒,我只需抛出MoreThan30SecondsException异常?

11个回答

81
您可以在单独的线程中运行操作,然后在线程加入操作上设置超时时间:
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.");
        }
    }
}

1
@Bomboca:我撤销了你的编辑,我所抛出的“Exception”不应该是“ThreadAbortException”,这是在调用“Abort”时由CLR抛出的异常。 - Paolo Tedesco
2
这是一个阻塞调用,如果您需要主线程在此期间执行其他操作,那么这将不起作用! - feldeOne

27

更简单的方法是使用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");

这非常简单,自 .net 4.0 起就可用。 - Anwar Chandra
@anwar 我会再次遇到这个答案,我正在使用.NET Framework 4.0,所以请让我纠正您:Task.Run是从.NET 4.5开始提供的。链接 - Emanuele Bellini
这不会结束线程。建议与CancellationToken一起使用。 - joe

8

如果您不想阻塞主线程,可以使用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();
}

Jon Skeet提供了一种比强制终止线程更温和的方法 (优雅地停止工作线程)。

但是,由于你无法控制PerformInitTransaction()正在执行的操作,因此当Abort失败并使对象处于无效状态时,你很难采取任何行动。如前所述,如果你能够清理aborting PerformInitTransaction留下的任何问题,你可以通过捕获ThreadAbortException这一异常来实现,但由于这是第三方调用,这意味着你必须猜测你已经离开他们的方法处于什么状态。

PerformInitTransaction应该真正提供超时。


1
文章“优雅地关闭工作线程”的链接已经失效。我可以建议使用https://jonskeet.uk/csharp/threads/shutdown.html。 - Alan Mark Kristensen

5
以下是两个实现,它们会抛出内部任务发生的任何异常。
对于没有返回值的操作:
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;
}

3
我认为这是最简单的方法:
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(); 
});

为什么这个被点赞了?它没有解决问题中所问的内容。这个代码会等待一段时间再执行操作,但是原始问题中要求如果操作耗时太长就应该失败。 - jhmckimm

2

在像这样的操作中,你需要小心终止它,尤其是在一个第三方组件中,你(可能)没有访问修改代码的权限。

如果你终止了操作,那么你就不知道你留下了基础类的什么状态。例如,它可能已经获取了一个锁,而你的终止导致该锁未被释放。即使你在终止操作后销毁对象,它可能已经改变了一些对它全局的状态,因此你将无法可靠地创建一个新实例而不需要重新启动。


1

你可以考虑在一个线程中调用该方法,在超时后终止线程并引发异常。此外,你还需要处理 ThreadBorted 异常。


1

.NET 6 / C# 10 的新方法:

var task = Task.Run(() => SomeMethod(input));
return await task.WaitAsync(TimeSpan.FromSeconds(30));

0

我刚在一个.NET 4.0应用程序中遇到了这个问题(没有访问Task.RunTask.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

这里有一个不错的通用解决方案的示例,使用了一个帮助类这里

它使用Action委托来避免前面示例所显示的线程创建/销毁。

希望这能帮到你。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接