怎样才能避免主线?

时间:2022-02-20 21:03:20

I'm doing a patch to solve a progress bar issue in an application that is a bit mess-up. The cancel on the progress bar used to do a Thread.Abort on the thread doing the heavy work. I changed that to raising a cancel flag that I can check at strategic place in the thread.

我正在做一个补丁来解决应用程序中的进度条问题,这有点混乱。进度条上的取消用于执行线程。在线程上中止执行繁重的工作。我将其更改为升起一个取消标志,我可以在线程的策略位置检查它。

Most of the time it works fine but once in a while the cancellation doesn't work at all. I suppose I could do a Application.DoEvents before looking at the status of the flag (there is no risk of reentry) but I would like a more "clean" option.

大多数情况下它都可以工作,但偶尔取消也不行。我想我可以申请一下。DoEvents在查看标志的状态之前(不存在重新进入的风险),但是我希望有一个更“干净”的选项。

I would appreciate if someone could provide me information to understand what exactly is going on and how this stuff works behind the scene. I would like to know how to deal with this issue without using the BackgroundWorker (like you would in .net 1.1) but I would also like to know if the BackgroundWorker solve that kind of problems and how it does it.

如果有人能给我提供一些信息,让我了解到底发生了什么,以及这些东西在幕后是如何工作的,我将不胜感激。我想知道如何不用BackgroundWorker(就像。net 1.1中的你一样)来处理这个问题,但是我也想知道BackgroundWorker是否解决了这类问题以及它是如何解决的。

Edit: I'm taking notes of you suggestions and will try some tomorrow and report back. I used a volatile bool at first by I think I updated it to an automatic property and forgot about the volatile. Could the worker thread keeps looking for the cached value again and again? I don't see how I could have a deathlock. The worker to check for the flag since I managed to break there with by placing a breakpoint on-the-fly. I always test with the same set of data and most of the time it cancels just fine. The only thing that change between tests is the moment I press cancel. So far, I only tested in debug, started from VS.

编辑:我会把你的建议记下来,明天再试试,然后汇报。一开始我用的是挥发性的bool,我把它升级为自动属性,忘记了挥发性。工作线程是否可以一次又一次地寻找缓存的值?我不明白我怎么会有死锁。工作人员检查标志,因为我设法通过动态放置断点来打破它。我总是用同一组数据进行测试,而且大多数时候它会被取消。在测试之间唯一改变的是我按下取消的那一刻。到目前为止,我只在debug中测试过,从VS开始。

Edit 2: It turns out that my issue isn't related to my flag or anything I added. It's more of a WinForm issue. The program get to call a ShowDialog (and there is already another ShowDialog blocked). I cannot drag the form and it doesn't refresh by itself. The cancel button on it doesn't even works. Here is the call stack when I pause everything.

编辑2:原来我的问题与我的旗帜或我添加的任何东西无关。这更像是一个WinForm问题。程序调用一个ShowDialog(已经有一个ShowDialog被阻塞)。我无法拖动表单,它本身也不会刷新。它的取消按钮甚至都不能工作。这是我暂停一切的调用堆栈。

[Code externe]  
    Mrnf.Son.Commun.dll!Mrnf.Son.Commun.Messages.BarreProgressionBase.ShowDialog(System.Windows.Forms.IWin32Window fenetre = {Mrnf.Son.Presentation.Windows.UI.Echanges.AssistantForm}) Ligne 274 + 0xb octets  C#
    Mrnf.Son.Commun.dll!Mrnf.Son.Commun.Controleurs.Utils.AttendreFinTraitement(System.Windows.Forms.Form parent = {Mrnf.Son.Presentation.Windows.UI.Echanges.AssistantForm}, Mrnf.Son.Commun.Messages.BarreProgressionBase progressionBase = {Mrnf.Son.Commun.Messages.BarreProgressionMessage}, System.Threading.Thread thread = {System.Threading.Thread}) Ligne 302 + 0xd octets    C#
    Mrnf.Son.Affaires.dll!Mrnf.Son.Affaires.Persisteurs.Echanges.LecteurDBFGeneriqueCollection.Importer(System.Windows.Forms.Form parent = {Mrnf.Son.Presentation.Windows.UI.Echanges.AssistantForm}) Ligne 95 + 0x1d octets    C#
    Mrnf.Son.Affaires.dll!Mrnf.Son.Affaires.Persisteurs.Echanges.PersisteurModeleEchanges.Importer(Mrnf.Son.Affaires.Entites.Echanges.ModeleEchanges unModele = {Mrnf.Son.Presentation.Windows.Controleurs.Echanges.ModeleEchanges.ModeleEchangesGenerique}, System.Windows.Forms.Form formParent = {Mrnf.Son.Presentation.Windows.UI.Echanges.AssistantForm}) Ligne 1880 + 0xd octets  C#
    Mrnf.Son.Affaires.dll!Mrnf.Son.Affaires.Entites.Echanges.ModeleEchanges.Importer(System.Windows.Forms.Form formParent = {Mrnf.Son.Presentation.Windows.UI.Echanges.AssistantForm}) Ligne 875 + 0x18 octets  C#
    Mrnf.Son.Presentation.Windows.exe!Mrnf.Son.Presentation.Windows.UI.Echanges.AssistantForm.EffectuerImport(Mrnf.Son.Affaires.Entites.Echanges.IModeleEchanges modele = {Mrnf.Son.Presentation.Windows.Controleurs.Echanges.ModeleEchanges.ModeleEchangesGenerique}) Ligne 1429 + 0xc octets  C#
    Mrnf.Son.Presentation.Windows.exe!Mrnf.Son.Presentation.Windows.UI.Echanges.AssistantForm._terminerBtn_Click(object sender = {Text = Impossible d'évaluer l'expression, car un frame natif se trouve en haut de la pile des appels.}, System.EventArgs e = {System.EventArgs}) Ligne 1334 + 0x1d octets C#
[Code externe]  
    Mrnf.Son.Presentation.Windows.exe!Mrnf.Son.Presentation.Windows.UI.Echanges.AssistantForm.WndProc(ref System.Windows.Forms.Message m = {System.Windows.Forms.Message}) Ligne 1133 + 0xb octets C#
[Code externe]  
    Mrnf.Son.Presentation.Windows.exe!Mrnf.Son.Presentation.Windows.Controleurs.Sondages.ActionsSondages.OnImporterSysExt() Ligne 1362 + 0x1f octets    C#
    Mrnf.Son.Presentation.Windows.exe!Mrnf.Son.Presentation.Windows.UI.Sondages.UEExploitationVue._mniImporterSysExt_Click(object sender = {System.Windows.Forms.ToolStripMenuItem}, System.EventArgs e = {System.EventArgs}) Ligne 820 + 0x12 octets   C#
[Code externe]  
    Mrnf.Son.Presentation.Windows.exe!Mrnf.Son.Presentation.Windows.Program.Main() Ligne 148 + 0x8 octets   C#
[Code externe]  

Edit 3: If I pass null to the ShowDialog it works fine (the UI doesn't freeze, the cancel button works, it cancels fine). I don't really understand the magic behind all this.

编辑3:如果我将null传递给ShowDialog,它会工作得很好(UI不会冻结,取消按钮会工作,它会被取消)。我真的不明白这背后的魔力。

4 个解决方案

#1


1  

The other posts are probably on track as to why.

其他的帖子可能也在讨论原因。

I like to use WaitHandles in conjunction with Thread.Join()/Thread.Abort() when I'm trying to kill a thread.

我喜欢将WaitHandles与thread. join ()/ thread. abort()结合使用。

private readonly ManualResetEvent _ExitThreadsEvent = new ManualResetEvent(false);
private Thread _MyThread;

public void Stop()
{
    _ExitThreadsEvent.Set();

    if (_MyThread != null)
    {
        if (!_MyThread.Join(5000))
        {
            _MyThread.Abort();
        }

        _MyThread = null;
    }
}

private void MyThread()
{
    if (!_ExitThreadsEvent.WaitOne(1))
    {
        // Do some work...
    }

    if (!_ExitThreadsEvent.WaitOne(1))
    {
        // Do some more work...
    }
}

Probably good to figure out your original deadlock issue too though.

不过,最好也弄清楚您最初的死锁问题。

#2


4  

BackgroundWorker doesn't do anything extra here, except to provide that flag in an easy to check location. So there are a few possibilities:

BackgroundWorker在这里不做任何额外的事情,除了在一个容易检查位置的地方提供这个标志。所以有一些可能性:

  • your code is getting to check the flag, but not noticing that it has changed (theoretically possible if the flag isn't synchronized or volatile, but very unlikely in non-trivial code)
  • 您的代码正在检查标志,但没有注意到它已经更改(理论上有可能,如果标志没有同步或不稳定,但在非平凡的代码中非常不可能)
  • your code isn't getting to check the flag
  • 您的代码不能检查标志

We'll assume the latter; a few common causes of that:

我们假设后者;以下是一些常见的原因:

  • you are accidentally deadlocking yourself (perhaps trying to Invoke back to the UI thread which is already waiting for a background thread, or getting into a loop around a lock or similar)
  • 您正在意外地使自己死锁(可能尝试调用已经在等待后台线程的UI线程,或者进入一个锁或类似的循环)
  • you are calling out over COM (or similar), and that call is never completing - a particularly hard scenario to escape from in some cases
  • 您正在通过COM(或类似的)调用,而这个调用永远不会完成——在某些情况下,这是一个特别难以避免的场景

Can you narrow down which of these it is? Perhaps inject some code to track what your thread is doing at intervals, and ensure it is doing something useful - or if not, track where it is getting stuck.

你能不能缩小范围?也许注入一些代码来跟踪线程每隔一段时间正在做什么,并确保它正在做一些有用的事情——或者如果没有,跟踪它被卡住的地方。

#3


2  

Application.DoEvents is a means to allow pending events in the message pump to be processed. Normally should have absolutely nothing to do with your background thread.

应用程序。DoEvents是允许处理消息泵中的未决事件的一种方法。通常应该与你的后台线程完全无关。

If the the cancel 'doesn't work at all', the solution will largely depend on what 'doesn't work at all' mean. Are you unable to change the flag? Is the UI stuck? Does the background thread not respond to the flag chane? Is it something else? The solution depends primarily on what exactly the problem is. It could be that you aren't checking the flag from the background, it could be that you deadlock the two threads. Showing the code, or elaborating the problem details would help.

如果“取消”完全不起作用,那么解决方案将很大程度上取决于“根本不起作用”的含义。你不能换国旗吗?UI困吗?后台线程是否对旗子没有响应?这是别的东西吗?解决方案主要取决于问题到底是什么。可能是您没有在后台检查标志,也可能是您死锁了两个线程。显示代码或详细说明问题细节将有所帮助。

#4


2  

This is almost always easy to debug. When you see the worker thread ignoring the cancel request, use Debug + Break All. Then Debug + Windows + Threads and double-click the worker thread. Then look at the call stack to see what the thread is doing and why it doesn't pass through the code that checks the flag.

这通常很容易调试。当看到工作线程忽略取消请求时,使用Debug + Break All。然后调试+ Windows +线程,双击工作线程。然后查看调用堆栈,看看线程在做什么,以及为什么它不通过检查标志的代码。

Beware that you have to declare the flag member with the volatile keyword. This prevents the JIT compiler from generating machine code that loads the member value in a register and never checking the actual variable value in memory. Which is liable to happen when you run the Release version of your program without a debugger. In that case, be sure to use Tools + Attach to Process to attach the debugger before you use the Break All command.

注意,必须使用volatile关键字声明标志成员。这可以防止JIT编译器生成机器代码,该代码在寄存器中加载成员值,并且永远不会检查内存中的实际变量值。当您在没有调试器的情况下运行程序的发布版本时,可能会发生这种情况。在这种情况下,在使用Break All命令之前,请确保使用Tools + Attach to Process来附加调试器。

A ManualResetEvent, checked with a WaitOne(0) call is better.

使用WaitOne(0)调用检查ManualResetEvent会更好。

#1


1  

The other posts are probably on track as to why.

其他的帖子可能也在讨论原因。

I like to use WaitHandles in conjunction with Thread.Join()/Thread.Abort() when I'm trying to kill a thread.

我喜欢将WaitHandles与thread. join ()/ thread. abort()结合使用。

private readonly ManualResetEvent _ExitThreadsEvent = new ManualResetEvent(false);
private Thread _MyThread;

public void Stop()
{
    _ExitThreadsEvent.Set();

    if (_MyThread != null)
    {
        if (!_MyThread.Join(5000))
        {
            _MyThread.Abort();
        }

        _MyThread = null;
    }
}

private void MyThread()
{
    if (!_ExitThreadsEvent.WaitOne(1))
    {
        // Do some work...
    }

    if (!_ExitThreadsEvent.WaitOne(1))
    {
        // Do some more work...
    }
}

Probably good to figure out your original deadlock issue too though.

不过,最好也弄清楚您最初的死锁问题。

#2


4  

BackgroundWorker doesn't do anything extra here, except to provide that flag in an easy to check location. So there are a few possibilities:

BackgroundWorker在这里不做任何额外的事情,除了在一个容易检查位置的地方提供这个标志。所以有一些可能性:

  • your code is getting to check the flag, but not noticing that it has changed (theoretically possible if the flag isn't synchronized or volatile, but very unlikely in non-trivial code)
  • 您的代码正在检查标志,但没有注意到它已经更改(理论上有可能,如果标志没有同步或不稳定,但在非平凡的代码中非常不可能)
  • your code isn't getting to check the flag
  • 您的代码不能检查标志

We'll assume the latter; a few common causes of that:

我们假设后者;以下是一些常见的原因:

  • you are accidentally deadlocking yourself (perhaps trying to Invoke back to the UI thread which is already waiting for a background thread, or getting into a loop around a lock or similar)
  • 您正在意外地使自己死锁(可能尝试调用已经在等待后台线程的UI线程,或者进入一个锁或类似的循环)
  • you are calling out over COM (or similar), and that call is never completing - a particularly hard scenario to escape from in some cases
  • 您正在通过COM(或类似的)调用,而这个调用永远不会完成——在某些情况下,这是一个特别难以避免的场景

Can you narrow down which of these it is? Perhaps inject some code to track what your thread is doing at intervals, and ensure it is doing something useful - or if not, track where it is getting stuck.

你能不能缩小范围?也许注入一些代码来跟踪线程每隔一段时间正在做什么,并确保它正在做一些有用的事情——或者如果没有,跟踪它被卡住的地方。

#3


2  

Application.DoEvents is a means to allow pending events in the message pump to be processed. Normally should have absolutely nothing to do with your background thread.

应用程序。DoEvents是允许处理消息泵中的未决事件的一种方法。通常应该与你的后台线程完全无关。

If the the cancel 'doesn't work at all', the solution will largely depend on what 'doesn't work at all' mean. Are you unable to change the flag? Is the UI stuck? Does the background thread not respond to the flag chane? Is it something else? The solution depends primarily on what exactly the problem is. It could be that you aren't checking the flag from the background, it could be that you deadlock the two threads. Showing the code, or elaborating the problem details would help.

如果“取消”完全不起作用,那么解决方案将很大程度上取决于“根本不起作用”的含义。你不能换国旗吗?UI困吗?后台线程是否对旗子没有响应?这是别的东西吗?解决方案主要取决于问题到底是什么。可能是您没有在后台检查标志,也可能是您死锁了两个线程。显示代码或详细说明问题细节将有所帮助。

#4


2  

This is almost always easy to debug. When you see the worker thread ignoring the cancel request, use Debug + Break All. Then Debug + Windows + Threads and double-click the worker thread. Then look at the call stack to see what the thread is doing and why it doesn't pass through the code that checks the flag.

这通常很容易调试。当看到工作线程忽略取消请求时,使用Debug + Break All。然后调试+ Windows +线程,双击工作线程。然后查看调用堆栈,看看线程在做什么,以及为什么它不通过检查标志的代码。

Beware that you have to declare the flag member with the volatile keyword. This prevents the JIT compiler from generating machine code that loads the member value in a register and never checking the actual variable value in memory. Which is liable to happen when you run the Release version of your program without a debugger. In that case, be sure to use Tools + Attach to Process to attach the debugger before you use the Break All command.

注意,必须使用volatile关键字声明标志成员。这可以防止JIT编译器生成机器代码,该代码在寄存器中加载成员值,并且永远不会检查内存中的实际变量值。当您在没有调试器的情况下运行程序的发布版本时,可能会发生这种情况。在这种情况下,在使用Break All命令之前,请确保使用Tools + Attach to Process来附加调试器。

A ManualResetEvent, checked with a WaitOne(0) call is better.

使用WaitOne(0)调用检查ManualResetEvent会更好。