繁体   English   中英

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

[英]How to stop a thread if thread takes too long

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

我开始在线程中导出到文件。 而且我尝试中止按钮单击上的线程。 但这不起作用。

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

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

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

总是最好使用协作取消-线程定期检查协调结构,例如ManualResetEventCancellationToken 您不能使用布尔变量标志之类的简单变量,因为这会导致争用情况,例如,如果两个或多个线程尝试同时设置它。

您可以在MSDN的“ 托管线程中的取消”部分中了解有关.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);
    }

您可以使用布尔标志。 为此使用易失性布尔值。

在助手中执行以下操作:

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

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

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

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

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

由于Thread.Abort()由另一个线程执行,因此它可以随时发生,并且在发生时会在目标线程上引发ThreadAbortException。

ExportHelper.DataTableToCsv:内部ExportHelper.DataTableToCsv:

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

StopButtonClick

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

要停止线程,您可以使用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();
    }
}

//来源::书->用C#编程

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM