【问题标题】:How to stop a thread if thread takes too long如果线程花费太长时间,如何停止线程
【发布时间】:2017-02-16 08:38:19
【问题描述】:

我有一种情况,我将数据导出到一个文件,我被要求做的是提供一个取消按钮,如果导出时间过长,单击该按钮将停止导出。

我开始在线程中导出到文件。我尝试在按钮单击时中止线程。但它不起作用。

我在 Google 上搜索,发现不推荐使用 abort()。但是我还应该选择什么来实现它?

我当前的代码是:

private void ExportButtonClick(object param)
{
    IList<Ur1R2_Time_Points> data = ct.T_UR.ToList();
    DataTable dtData = ExportHelper.ToDataTable(data);
    thread = new Thread(new ThreadStart(()=>ExportHelper.DataTableToCsv(dtData, "ExportFile.csv")));
    thread.SetApartmentState(ApartmentState.STA);
    thread.IsBackground = true;
    thread.Name = "PDF";
    thread.Start();
}

private void StopButtonClick(object param)
{
    if (thread.Name == "PDF")
    {
        thread.Interrupt();
        thread.Abort();
    }
}

【问题讨论】:

  • 你需要修复导致文件保存时间过长的错误,而不是调用thread.Abort(),这可能会以泪水告终。
  • 使用取消而不是中止。 stackoverflow.com/questions/3632149/…
  • @MatthewWatson 这是一个大文件。没有错误
  • DataTableToCsv 是做什么的?请贴出它的源代码。 那是应该修改以允许取消的方法,而不是封闭线程。顺便说一句,为什么不使用Task.Run 而不是原始线程?
  • @tosttesttosttest 那么你肯定做错了 - 从单个数据表生成 800 十亿 行?即使数字是正确的,您也应该使用数据库的导出工具。他们都有这样的设施。

标签: c# .net multithreading kill abort


【解决方案1】:

中止线程是个坏主意,尤其是在处理文件时。您将没有机会清理写一半的文件或清理不一致的状态。

它不会损害 .NET 运行时 bat,它可能会损害您自己的应用程序,例如,如果工作方法使全局状态、文件或数据库记录处于不一致的状态。

最好使用 cooperative 取消 - 线程会定期检查协调构造,如 ManualResetEventCancellationToken。您不能使用像布尔标志这样的简单变量,因为这会导致竞争条件,例如如果两个或多个线程尝试同时设置它。

您可以在 MSDN 的 Cancellation in Managed Threads 部分阅读 .NET 中的取消。

在 .NET 4 中添加了 CancellationToken/CancellationTokenSource 类,使取消事件比传递事件更容易。

在您的情况下,您应该修改您的DataTableToCsv 以接受CancellationToken。该令牌由CancellationTokenSource 类生成。

当您调用 CancellationTokenSource.Cancel 时,令牌的 IsCancellationRequested 属性变为 true。您的 DataTableToCsv 方法应定期检查此标志。如果设置了,它应该退出任何循环,删除任何不一致的文件等。

CancelAfter 直接支持超时。本质上,CancelAfter 启动了一个计时器,当它到期时将触发Cancel

您的代码可能如下所示:

CancellationTokenSource _exportCts = null;

private void ExportButtonClick(object param)
{
    IList<Ur1R2_Time_Points> data = ct.T_UR.ToList();
    DataTable dtData = ExportHelper.ToDataTable(data);

    _exportCts=new CancellationTokenSource();
    var token=_exportCts.Token;

    thread = new Thread(new ThreadStart(()=>
            ExportHelper.DataTableToCsv(dtData, "ExportFile.csv",token)));
    thread.SetApartmentState(ApartmentState.STA);
    thread.IsBackground = true;
    thread.Name = "PDF";

    _exportCts.CancelAfter(10000);
    thread.Start();

}


private void StopButtonClick(object param)
{
    if (_exportCts!=null)
    {
        _exportCts.Cancel();
    }
}

DataTableToCsv 应该包含类似这样的代码:

foreach(var row in myTable)
{
    if (token.IsCancellationRequested)
    {
        break;
    }
    //else continue with processing
    var line=String.Join(",", row.ItemArray);
    writer.WriteLine(line);

}

你可以通过使用任务而不是原始线程来清理你的代码:

private async void ExportButtonClick(object param)
{
    IList<Ur1R2_Time_Points> data = ct.T_UR.ToList();
    DataTable dtData = ExportHelper.ToDataTable(data);

    _exportCts=new CancellationTokenSource();
    var token=_exportCts.Token;

    _exportCts.CancelAfter(10000);
    await Task.Run(()=> ExportHelper.DataTableToCsv(dtData, "ExportFile.csv",token)));
    MessageBox.Show("Finished");
}

您还可以通过使用异步操作来加速它,例如从数据库读取数据或写入文本文件不阻塞或使用线程。 Windows IO(文件和网络)在驱动程序级别是异步的。像File.WriteLineAsync 这样的方法不使用线程来写入文件。

您的导出按钮处理程序可能变成:

private void ExportButtonClick(object param)
{
    IList<Ur1R2_Time_Points> data = ct.T_UR.ToList();
    DataTable dtData = ExportHelper.ToDataTable(data);

    _exportCts=new CancellationTokenSource();
    var token=_exportCts.Token;

    _exportCts.CancelAfter(10000);
    await Task.Run(async ()=> ExportHelper.DataTableToCsv(dtData, "ExportFile.csv",token)));
    MessageBox.Show("Finished");
}

DataTableToCsv

public async Task DataTableToCsv(DataTable table, string file,CancellationToken token)
{
...
    foreach(var row in myTable)
    {
        if (token.IsCancellationRequested)
        {
            break;
        }
        //else continue with processing
        var line=String.Join(",", row.ItemArray);
        await writer.WriteLineAsync(line);
    }

【讨论】:

  • 提示:如果导出当前正在运行,则禁用ExportButtonClick,或者在_exportCts=new CancellationTokenSource() 之前执行_exportCts?.Cancel(),以便在重新创建之前取消(如果存在)。
  • 还要确保将令牌发送到Task.Run(() =&gt; {}, token),以便它与任务相关联。我不能说为什么,但我已经在 SO 上阅读了推荐的主题。
  • @Default 1) 用于在 CTS 已取消时阻止任务启动,将任务的状态设置为 Canceled 而不是 FaultedRanToCompletion 并返回 TaskCanceledException如果CancellationToken.ThrowIfCancellationRequested()Task Cancellation 对此进行了解释。
  • @Default 2) 在此处添加这将导致很长的答案和人为的示例。如果我们有DataTableToCsv() 的源代码,我们可以重写它事件处理程序来清理,以不同于故障的方式处理取消异常等。
  • 谢谢我用同样的方法解决了。坦克你这么多@PanagiotisKanavos
【解决方案2】:

您可以使用布尔标志。为此使用 volatile 布尔值。

在助手中执行如下操作:

 this.aborted = false;
 while(!finished && !aborted) {
      //process one row
 }

当你想取消操作时,你调用一个方法将 aborted 设置为 true:

 public void Abort() {
     this.aborted = true;
 }

【讨论】:

  • 是的,我已经看过了。但在我的情况下,我没有像 while(true) 这样的无限循环,当你必须中止时你设置为 false 。但是我正在导出一个文件,所以没有 while 循环将条件设置为 false 以中断无限迭代。所以不知道如何在我的情况下设置此文件条件,因为我正在做“ ThreadStart(()=>ExportHelper.DataTableToCsv(dtData, "ExportFile.csv")));"
  • @tosttesttosttest 然后根据需要经常检查。不要使用布尔 BTW,那是要求竞争条件。 .NET 有 CancellationTokenSource 和 CancellationToken 类。
【解决方案3】:

在这里阅读:https://msdn.microsoft.com/en-us/library/system.threading.threadabortexception(v=vs.110).aspx

当调用 Abort 方法来销毁线程时,公共语言运行时会引发 ThreadAbortException。 ThreadAbortException 是一个可以被捕获的特殊异常,但它会在 catch 块结束时再次自动引发。当引发此异常时,运行时会在结束线程之前执行所有 finally 块。因为线程可以在 finally 块中进行无限计算或调用 Thread.ResetAbort 来取消中止,所以不能保证线程将永远结束。如果要等到中止的线程结束,可以调用 Thread.Join 方法。 Join 是一个阻塞调用,直到线程真正停止执行才返回。

由于 Thread.Abort() 由另一个线程执行,它可以随时发生,当它发生时 ThreadAbortException 会在目标线程上抛出。

内部ExportHelper.DataTableToCsv:

catch(ThreadAbortException e) {
    Thread.ResetAbort();
}

开启StopButtonClick

if (thread.Name == "PDF")
{
    thread.Interrupt();
    thread.Join();
}

【讨论】:

  • 这如何回答有关取消导出的问题?他表示他确实不想想使用 Abort()
  • @Default 我以为他说他读到不推荐使用 abort()
【解决方案4】:

要停止线程,您有一个选项Thread.Abort。但是,因为此方法在目标线程被另一个线程执行时抛出了 ThreadAbortException。 不建议这样做。 停止线程的第二个选项是使用目标和调用线程都可以访问的共享变量。 请参阅示例 ::

public static class Program
{
    public static void ThreadMethod(object o)
    {
        for (int i = 0; i < (int)o; i++)
        {
            Console.WriteLine("ThreadProc: { 0}", i);
            Thread.Sleep(0);
        }
    }
    public static void Main()
    {
        bool stopped = false;
        Thread t = new Thread(new ThreadStart(() =>
        {
            while (!stopped)
            {
                Console.WriteLine("Running...");
                Thread.Sleep(1000);
            }
        }));
        t.Start();
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
        stopped = true;
        t.Join();
    }
}

//Source :: Book --> C#编程

【讨论】:

  • 请不要说Thread.Abort() 是一个选项。它不是。永远。
  • 我怀疑这本书说Thread.Abort 是一个非常糟糕的主意,合作取消是唯一真正的选择。如果是老书,会讲ManualResetEvent。较新的书籍将讨论 CancellationToken
猜你喜欢
  • 2014-07-29
  • 2017-11-18
  • 2015-03-21
  • 2017-03-03
  • 2010-11-12
  • 1970-01-01
  • 2015-11-28
  • 2019-08-24
  • 1970-01-01
相关资源
最近更新 更多