如何确保异步方法完成工作?

时间:2014-07-27 11:41:58

标签: c# .net multithreading winforms treeview

我对线程很新,所以我的想法和问题可能有点愚蠢:)

我使用来自另一个线程的数据填充WinForm控件,因此当我尝试访问控件时,我必须调用Invoke()

如果我理解正确,treeView.BeginInvoke(/*some Action()*/)会使Action<>()在主线程中运行。但我“解雇并忘记”这个BeginInvoke(),所以我不知道这项工作何时完成。即使工作线程关闭并且执行返回主线程,我也无法确定所有BeginInvoke()方法是否已完成执行。

这就是为什么即使在返回主线程后我也无法使用Control来解决BeginInvoke()。{/ p>

实际问题 TreeView.ExpandAll()不起作用。

请看下面的代码段。

private void btnGetTree_Click(object sender, EventArgs e) {
    var treeViewWriter = new Thread(() => UpdateTreeView(new AddXmlNodeArgs(di, null), treeDirectoryContents));
    treeViewWriter.Start();
    treeViewWriter.Join();
    treeDirectoryContents.ExpandAll();
}

// method runs on a worker thread
public static void UpdateTreeView(AddXmlNodeArgs args, TreeView treeView) {
    // I will miss details... Here is the code that I run for every new TreeNode:
    treeView.UpdateTree((TreeView tree) => {
        tree.Nodes[0].Nodes.Add(newTreeNode); // treeView.Nodes[0]...
    });
}

// Extension method for TreeView
public static void UpdateTree(this TreeView tree, Action<TreeView> code) {
    if (tree.InvokeRequired)
        tree.BeginInvoke(code, tree);
    else
        code.Invoke(tree);
}

我解雇了tree.BeginInvoke(),但我没有在任何地方拨打EndInvoke()。所以我想当btnGetTree_Click执行到达treeDirectoryContents.ExpandAll()时 - 并非所有Invoke()方法都完成了他们的工作。这就是为什么.ExpandAll() doesn't work

如果我错了,请纠正我,请就如何解决这个问题提出建议。

2 个答案:

答案 0 :(得分:1)

这是绝对错误的:

treeViewWriter.Start();
treeViewWriter.Join();

永远不要从主线程调用Thread.Join!因为Join冻结了应用程序,所有BeginInvoke / Invoke永远不会完全执行因为消息没有处理。

BeginInvoke()实际上是这样的:

  1. 它在消息循环上发送一些WM_USER(或类似内容)。
  2. 主线程在Application.DoEvents()中弹出此消息(或类似内容,总是在Application.Run()中调用)
  3. 主线程执行传递给BeginInvoke()
  4. 的委托
  5. 主线程表示执行结束(WaitHandle中的IAsyncResult
  6. EndInvoke()等待此类信号(或者如果IAsyncResult中的BeginInvoke从未存储,则会被垃圾收集)
  7. 所以再次:你只是把它写成事件驱动的或者做这样的事情:

    private bool done = false;
    void click(object, EventArgs) {
        thread.Start();
        while(!done) Application.DoEvents();
        tree.ExpandAll();
    }
    

    <强> ADDON: Eihter使用Invoke()(已同步)和上面的循环Application.DoEvents() 或者使用BeginInvoke()并以相同的方式调用ExpandAll(通过线程中的BeginInvoke())

    <强> ADDON2:

    private bool done;
    void click(object,EventArgs) {
        done = false; // init state
        new Thread(work).Start(); // start backgound work
        while(!done) Application.DoEvents(); // wait until done
        finish(); } // finish the job in main thread
    void work() {
        Thread.Sleep(100); // do your work
        done = true; } // signal done
    void finish() {
        whatever(); } // called on main thread
    
    void click2(object,EventArgs) {
        new Thread(work2).Start(); } // just start the hread
    void work2() {
        Thread.Sleep(100); // do your work
        BeginInvoke(new Action(finish)); } // execute finish() on main thread
    

答案 1 :(得分:0)

创建一个Action Invoke代表,然后BeginInvoke该代理。 这样您就可以使用回调函数将ExpandAll移动到:

if (tree.InvokeRequired)
        new Action(() => { tree.Invoke(code, tree); }).BeginInvoke((ar) => {
            treeDirectoryContents.ExpandAll();
        }, null);
else
    code.Invoke(tree);

请注意,我使用简单的BeginInvoke替换了您的原始Invoke

更新:由于firda正确提到,因为主线程在Join方法内被阻塞,等待另一个线程退出,所以在控件上执行Invoke将导致死锁。现在您的ExpandAll已移至回调,您应该删除Join,一切都会好的。