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

C# 如何确保异步方法完成工作?,c#,.net,multithreading,winforms,treeview,C#,.net,Multithreading,Winforms,Treeview,我对线程非常陌生,所以我的想法和问题可能有点愚蠢:) 我用另一个线程的数据填充WinForm控件,因此在尝试访问控件时必须调用Invoke() 如果我理解正确,treeView.BeginInvoke(/*someaction()*/)使这个Action()在主线程中运行。但是我“发射并忘记”了这个BeginInvoke(),所以我不知道这项工作何时真正完成。即使工作线程关闭,执行返回到主线程,我也不能确定所有的BeginInvoke()方法都已完成执行 这就是为什么即使在返回到主线程后,我也无

我对线程非常陌生,所以我的想法和问题可能有点愚蠢:)

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

如果我理解正确,
treeView.BeginInvoke(/*someaction()*/)
使这个
Action()
在主线程中运行。但是我“发射并忘记”了这个
BeginInvoke()
,所以我不知道这项工作何时真正完成。即使工作线程关闭,执行返回到主线程,我也不能确定所有的
BeginInvoke()
方法都已完成执行

这就是为什么即使在返回到主线程后,我也无法使用启动的
BeginInvoke()
控件来管理

实际问题是
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);
}
private void btnGetTree\u单击(对象发送者,事件参数e){
var treeViewWriter=newthread(()=>UpdateTreeView(newaddXMLNodeArgs(di,null),treeDirectory内容));
treeViewWriter.Start();
treeViewWriter.Join();
treeDirectory内容。ExpandAll();
}
//方法在工作线程上运行
publicstaticvoidupdatetreeview(AddXmlNodeArgs-args,TreeView-TreeView){
//我将错过细节…以下是我为每个新TreeNode运行的代码:
更新树((树视图树)=>{
tree.Nodes[0].Nodes.Add(newTreeNode);//treeView.Nodes[0]。。。
});
}
//TreeView的扩展方法
publicstaticvoidupdatetree(此树视图树,操作代码){
if(tree.invokererequired)
tree.BeginInvoke(代码,tree);
其他的
调用(树);
}
我启动了
tree.BeginInvoke()
,但我不会在任何地方调用
EndInvoke()
。所以我猜当在
btnGetTree\u单击
execution到达
treeDirectoryContents.ExpandAll()
-不是所有的
Invoke()
方法都完成了它们的工作。这就是为什么
.ExpandAll()不起作用的原因


如果我错了,请纠正我,并给出如何解决此问题的建议。

创建一个
操作
,该操作调用代理,然后
开始调用该操作。
这样,您将有一个回调,您可以将
ExpandAll
移动到:

if (tree.InvokeRequired)
        new Action(() => { tree.Invoke(code, tree); }).BeginInvoke((ar) => {
            treeDirectoryContents.ExpandAll();
        }, null);
else
    code.Invoke(tree);
注意,我用一个简单的
调用
替换了您原来的
BeginInvoke


更新:正如firda正确提到的,由于主线程在等待另一个线程退出的
Join
方法中被阻塞,因此对控件执行
Invoke
将导致死锁。因此,现在您的
ExpandAll
被移动到回调中,您应该删除
Join
,一切都会好起来。

这是绝对错误的:

treeViewWriter.Start();
treeViewWriter.Join();
永远不要调用线程。从主线程加入因为
Join
会冻结应用程序,所有
BeginInvoke
/
Invoke
都不会完全执行,因为消息没有得到处理

这就是
BeginInvoke()
的实际工作原理:

  • 它在消息循环中发送一些WM_用户(或类似用户)
  • 主线程在
    Application.DoEvents()
    中弹出此消息(或在
    Application.Run()
    中始终调用的类似程序)
  • 主线程执行传递给
    BeginInvoke()
  • 主线程发出执行结束的信号(通过
    IAsyncResult
    中的
    WaitHandle
  • EndInvoke()
    等待这样的信号(或者如果
    BeginInvoke
    中的
    IAsyncResult
    从未存储,它将被垃圾收集)
  • 所以再说一遍:您可以编写纯事件驱动的程序,或者执行以下操作:

    private bool done = false;
    void click(object, EventArgs) {
        thread.Start();
        while(!done) Application.DoEvents();
        tree.ExpandAll();
    }
    
    插件: Eihter使用
    Invoke()
    (已同步)并使用
    Application.DoEvents()

    或者使用BeginInvoke()并以相同的方式调用Expander(通过线程中的BeginInvoke()调用)

    附件2:

    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
    

    为什么要创建一个线程,然后在
    btnGetTree\u单击
    中等待它完成?这里面不需要线usage@L.B我只发布了您了解我的问题所需的代码。我在
    btnGetTree\u Click
    中有多个线程,它们像“生产者/多消费者”一样工作。所有这些最初都是从这里开始的:@jamesbong似乎是学习TPL库和异步/等待的好时机。启动多个任务,然后等待任务。当所有(…)
    应用程序。DoEvents()
    在主线程中执行繁重的工作并使用它至少获得一些响应时,它是不好的。如果您在其他线程中完成繁重的工作,并在这样的while(!done)Application.DoEvents()循环中等待它完成,这是绝对好的。。。但是:在背景工作结束时,通常有一个纯事件的方式:)。新线程(DoWork).Start()void DoWork(){doit();done=true;}……更好的方法是调用像BeginInvoke(Finish)这样的函数,然后在那里(循环之后)执行您想要执行的操作,然后执行代码?您可能做错了什么,所以请再说一遍:要么使用while(!done)。。。并且只调用!(无开始唤醒)或始终开始唤醒,即使是最后一次完成呼叫。你选了哪一个。。。。看看我的原始答案和最后一个例子(经过测试!)创建一个新的极简项目(就像我在那里为你做的那样)并重现这个问题。学习如何报告问题(第一个评论你第一个问题的人告诉你)。我可以修改你的代码,但为什么?这样你什么也学不到。首先要了解问题,这里有所有信息(你说你的应用程序冻结了,我说永远不要在主线程中使用Thread.Join(),我100%肯定你这么做了)。BeginInvoke()更好,不用担心,框架程序员很聪明,使用队列(将所有连续的BeginInvoke推送到队列,直到主线程弹出消息并执行所有排队的deleget)