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)