如何避免主线挨饿?

时间:2010-02-16 22:23:40

标签: c# .net

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

大部分时间它工作正常但偶尔取消根本不起作用。我想在查看旗帜的状态之前我可以做Application.DoEvents(没有重新进入的风险),但我想要一个更“干净”的选项。

如果有人能够向我提供信息以了解究竟发生了什么以及这些东西如何在幕后工作,我将不胜感激。我想知道如何在不使用BackgroundWorker的情况下处理这个问题(就像在.net 1.1中那样),但我也想知道BackgroundWorker是否解决了这类问题及其如何解决可以。

编辑:我正在记录你的建议,并会在明天尝试一些并报告回来。我最初使用了挥发性bool,我认为我将其更新为自动属性并忘记了volatile。工作线程可以一次又一次地查找缓存的值吗?我不知道怎么会有死锁。工作人员检查标志,因为我设法通过在运行中放置一个断点来打破那里。我总是使用相同的数据集进行测试,大部分时间它都取消了。测试之间唯一的变化就是我按下取消的那一刻。到目前为止,我只在调试中测试过,从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它工作正常(UI不冻结,取消按钮工作,它取消了很好)。我真的不明白这一切背后的魔力。

4 个答案:

答案 0 :(得分:4)

BackgroundWorker此处不执行任何操作 extra ,除非在易于检查的位置提供该标记。所以有一些可能性:

  • 你的代码正在检查标志,但没有注意到它已经改变了(理论上如果标志不同步或不稳定,但在非平凡的代码中不太可能)
  • 您的代码无法检查标记

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

  • 你不小心把自己弄死了(也许是试图Invoke回到已经在等待后台线程的UI线程,或者进入一个锁或类似的循环中)
  • 你正在呼叫COM(或类似的),并且该呼叫永远不会完成 - 在某些情况下逃脱的特别困难的场景

你能缩小它们中的哪一个吗?也许注入一些代码来跟踪你的线程每隔一段时间做什么,并确保它正在做一些有用的事情 - 如果没有,跟踪它被卡住的地方。

答案 1 :(得分:2)

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

如果取消'根本不起作用',解决方案将在很大程度上取决于“根本不起作用”的含义。你不能改变旗帜吗?用户界面被卡住了吗?后台线程是否不响应标志chane?还有别的吗?解决方案主要取决于问题究竟是什么。可能是你没有从后台检查标志,可能是你使两个线程死锁。显示代码或详细说明问题详细信息会有所帮助。

答案 2 :(得分:2)

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

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

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

答案 3 :(得分:1)

其他帖子可能正在跟踪原因。

当我试图杀死一个线程时,我喜欢将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...
    }
}

虽然可能很好地找出原来的死锁问题。