简体   繁体   English

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

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

I have a situation that i export data to a file and what i have been asked to do is to provide a cancel button which on click will stop the export if it takes too much time to export. 我有一种情况,我将数据导出到文件中,而我被要求做的是提供一个取消按钮,如果导出时间太长,单击该按钮将停止导出。

I started exporting to the file in a thread. 我开始在线程中导出到文件。 And i try to abort the thread on the button click. 而且我尝试中止按钮单击上的线程。 But it do not work. 但这不起作用。

I searched on Google and i found that abort() is not recommended. 我在Google上进行了搜索,发现不建议使用abort()。 But what else should I choose to achieve it? 但是我还应该选择什么来实现它呢?

My current code is: 我当前的代码是:

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

Aborting a thread is a bad idea, especially when dealing with files. 中止线程是一个坏主意,尤其是在处理文件时。 You won't have a chance to clean up half-written files or clean-up inconsistent state. 您将没有机会清理半写文件或清理不一致的状态。

It won't harm the .NET Runtime bat it can hurt your own application eg if the worker method leaves global state, files or database records in an inconsistent state. 它不会损害.NET运行时,它可能会损害您自己的应用程序,例如,如果worker方法使全局状态,文件或数据库记录处于不一致状态。

It's always preferable to use cooperative cancellation - the thread periodically checks a coordination construct like a ManualResetEvent or CancellationToken . 总是最好使用协作取消-线程定期检查协调结构,例如ManualResetEventCancellationToken You can't use a simple variable like a Boolean flag, as this can lead to race conditions, eg if two or more threads try to set it at the same time. 您不能使用布尔变量标志之类的简单变量,因为这会导致争用情况,例如,如果两个或多个线程尝试同时设置它。

You can read about cancellation in .NET in the Cancellation in Managed Threads section of MSDN. 您可以在MSDN的“ 托管线程中的取消”部分中了解有关.NET中的取消的信息。

The CancellationToken/CancellationTokenSource classes were added in .NET 4 to make cancellation easier that passing around events. 在.NET 4中添加了CancellationToken / CancellationTokenSource类,以使取消操作比传递事件更容易。

In your case, you should modify your DataTableToCsv to accept a CancellationToken . 在您的情况下,您应该修改DataTableToCsv以接受CancellationToken That token is generated by a CancellationTokenSource class. 该令牌由CancellationTokenSource类生成。

When you call CancellationTokenSource.Cancel the token's IsCancellationRequested property becomes true. 当您调用CancellationTokenSource.Cancel时 ,令牌的IsCancellationRequested属性变为true。 Your DataTableToCsv method should check this flag periodically. 您的DataTableToCsv方法应定期检查此标志。 If it's set, it should exit any loops, delete any inconsistent files etc. 如果设置,它将退出任何循环,删除所有不一致的文件等。

Timeouts are directly supported with CancelAfter . CancelAfter直接支持超时。 Essentially, CancelAfter starts a timer that will fire Cancel when it expires. 本质上, CancelAfter启动一个计时器,该计时器将在其到期时触发Cancel

Your code could look like this: 您的代码可能如下所示:

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 should contain code similar to this: DataTableToCsv应包含与此类似的代码:

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

}

You can clean up your code quite a bit by using tasks instead of raw threads: 您可以使用任务而不是原始线程来清理代码:

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

You could also speed it up by using asynchronous operations, eg to read data from the database or write to text files without blocking or using threads . 您也可以通过使用异步操作来加快速度,例如从数据库中读取数据或写入文本文件而不会阻塞或使用线程 Windows IO (both file and network) is asynchronous at the driver level. Windows IO(文件和网络)在驱动程序级别是异步的。 Methods like File.WriteLineAsync don't use threads to write to a file. 诸如File.WriteLineAsync之类的方法不使用线程来写入文件。

Your Export button handler could become : 您的“导出”按钮处理程序可能变为:

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

and DataTableToCsv : 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);
    }

You can use a boolean flag. 您可以使用布尔标志。 Use a volatile boolean for that. 为此使用易失性布尔值。

In the helper do something like: 在助手中执行以下操作:

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

Whenever you want to cancel the operation, you call a method to set aborted to true: 每当您想要取消操作时,就调用一个方法来将aborted设置为true:

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

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

When a call is made to the Abort method to destroy a thread, the common language runtime throws a ThreadAbortException. 调用Abort方法销毁线程时,公共语言运行库将引发ThreadAbortException。 ThreadAbortException is a special exception that can be caught, but it will automatically be raised again at the end of the catch block. ThreadAbortException是可以捕获的特殊异常,但是它将在catch块的末尾自动再次引发。 When this exception is raised, the runtime executes all the finally blocks before ending the thread. 引发此异常时,运行时将在结束线程之前执行所有的finally块。 Because the thread can do an unbounded computation in the finally blocks or call Thread.ResetAbort to cancel the abort, there is no guarantee that the thread will ever end. 因为线程可以在finally块中执行无界计算或调用Thread.ResetAbort取消中止,所以不能保证线程将永远结束。 If you want to wait until the aborted thread has ended, you can call the Thread.Join method. 如果要等待中止的线程结束,则可以调用Thread.Join方法。 Join is a blocking call that does not return until the thread actually stops executing. Join是一个阻塞调用,直到线程实际停止执行后才返回。

Since Thread.Abort() is executed by another thread, it can happen anytime and when it happens ThreadAbortException is thrown on target thread. 由于Thread.Abort()由另一个线程执行,因此它可以随时发生,并且在发生时会在目标线程上引发ThreadAbortException。

Inside ExportHelper.DataTableToCsv: ExportHelper.DataTableToCsv:内部ExportHelper.DataTableToCsv:

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

On StopButtonClick StopButtonClick

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

To Stop a thread you have one option of Thread.Abort .However because this method thrown ThreadAbortException on the target thread when it executed by another thead. 要停止线程,您可以使用Thread.Abort一个选项。但是,由于该方法在由另一个主题执行时会在目标线程上引发ThreadAbortException。 Which is not recommended. 不建议这样做。 The second option to stop a thread is by using shared variable that both your target and your calling thread can access. 停止线程的第二种方法是使用目标和调用线程都可以访问的共享变量。 See the 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 --> Programming in c# //来源::书->用C#编程

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

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