如果线程花费太长时间如何停止线程

时间:2017-02-16 08:38:19

标签: c# .net multithreading kill abort

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

我开始在一个帖子中导出到该文件。我尝试在点击按钮上中止线程。但它不起作用。

我在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();
    }
}

4 个答案:

答案 0 :(得分:3)

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

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

使用合作取消总是更可取 - 线程会定期检查协调结构,如ManualResetEventCancellationToken。你不能使用像布尔标志这样的简单变量,因为这会导致竞争条件,例如,如果两个或多个线程试图同时设置它。

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

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

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

当您致电CancellationTokenSource.Cancel时,令牌的IsCancellationRequested属性变为真。您的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);
    }

答案 1 :(得分:2)

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

在助手中执行以下操作:

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

每当您想要取消操作时,您都可以调用一个方法将aborted设置为true:

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

答案 2 :(得分:0)

请在此处阅读: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();
}

答案 3 :(得分:0)

要停止一个线程,你有Thread.Abort的一个选项。但是因为这个方法在由另一个thead执行时在目标线程上抛出ThreadAbortException。 不建议这样做。 停止线程的第二个选项是使用目标和调用线程都可以访问的共享变量。 参见Example ::

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 - &gt;用c#编程