代码之家  ›  专栏  ›  技术社区  ›  Simon T.

如何避免让主线挨饿?

  •  5
  • Simon T.  · 技术社区  · 15 年前

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

    大多数情况下,它可以正常工作,但偶尔取消根本不起作用。我想我可以做一个 Application.DoEvents 在查看标志的状态之前(没有重新进入的风险),但我希望有一个更“干净”的选项。

    如果有人能向我提供信息,让我了解到底发生了什么,以及这些东西在幕后是如何工作的,我将不胜感激。我想知道如何处理这个问题而不使用 BackgroundWorker (就像你在.NET 1.1中那样)但是我也想知道 背景工作者 解决这类问题以及解决方法。

    编辑:我会记下你的建议,明天再试一次,然后再汇报。起初我使用了一个易失性bool,我想我把它更新到了一个自动属性,忘记了易失性。工作线程是否可以一次又一次地查找缓存值?我不知道我怎么会死。检查标志的工作程序,因为我通过在运行中放置一个断点成功地在那里中断了标志。我总是用相同的数据集进行测试,大多数时候它取消的时候都很好。测试之间唯一的变化就是我按下取消键的那一刻。到目前为止,我只在调试中测试,从vs.

    编辑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]  
    

    编辑3:如果我通过 null 对于ShowDialog,它工作正常(用户界面不冻结,取消按钮工作,取消正常)。我不太明白这一切背后的魔力。

    4 回复  |  直到 14 年前
        1
  •  1
  •   Cory Charlton    15 年前

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

    我喜欢使用 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...
        }
    }
    

    不过,最好也能解决最初的死锁问题。

        2
  •  4
  •   Marc Gravell    15 年前

    BackgroundWorker 什么都不做 额外的 这里,除了在易于检查的位置提供该标志。所以有几个可能性:

    • 您的代码正在检查标志,但没有注意到它已经更改(理论上,如果标志不同步或不稳定,则可能发生这种情况,但在重要的代码中不太可能发生这种情况)
    • 您的代码无法检查标志

    我们将假设后者;一些常见的原因:

    • 你不小心把自己锁死了(也许是想 Invoke 返回到已经在等待后台线程的UI线程,或进入围绕锁或类似线程的循环)
    • 您通过COM(或类似方式)发出呼叫,但该呼叫永远无法完成-在某些情况下,这是一个特别难以逃避的场景。

    你能缩小范围吗?也许可以插入一些代码来跟踪线程的工作间隔,并确保它在做一些有用的事情——或者,如果没有,跟踪它被卡住的地方。

        3
  •  2
  •   Remus Rusanu    15 年前

    application.doEvents是一种允许处理消息泵中挂起事件的方法。通常应该与您的后台线程完全无关。

    如果取消“根本不起作用”,解决方案将主要取决于“根本不起作用”的含义。您是否无法更改标志?用户界面卡住了吗?后台线程是否不响应标志chane?这是别的东西吗?解决方案主要取决于问题的具体内容。可能是您没有从后台检查标志,也可能是死锁了两个线程。显示代码或详细描述问题细节将有帮助。

        4
  •  2
  •   Hans Passant    15 年前

    这几乎总是很容易调试的。当您看到工作线程忽略取消请求时,请使用debug+break all。然后调试+windows+线程并双击工作线程。然后查看调用堆栈,了解线程在做什么,以及为什么它不通过检查标志的代码。

    注意,必须用volatile关键字声明标志成员。这将阻止JIT编译器生成将成员值加载到寄存器中并且从不检查内存中实际变量值的机器代码。在没有调试器的情况下运行程序的发布版本时,很容易发生这种情况。在这种情况下,在使用break all命令之前,请确保使用tools+attach to process附加调试器。

    通过waitone(0)调用检查的manualResetevent更好。