【问题标题】:How to ensure that async method finished work?如何确保异步方法完成工作?
【发布时间】:2014-07-27 11:41:58
【问题描述】:

我对线程很陌生,所以我的想法和问题可能有点傻:)

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

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

这就是为什么即使在返回主线程后我也无法使用 Control 进行管理,我将 BeginInvoke() 解雇。

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

看看下面的代码sn-p。

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

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

【问题讨论】:

  • 为什么要创建一个线程,然后在btnGetTree_Click 中等待它完成?这种用法不需要​​线程
  • @L.B 我只发布了您需要查看以了解我的问题的代码。我在btnGetTree_Click 中有多个线程,它们的工作方式类似于“生产者/多个消费者”
  • 这一切最初都是从这里开始的:stackoverflow.com/questions/24975972/…
  • @jamesbong 似乎是学习 TPL 库和 async/await 的好时机。启动多个Tasks 然后await Task.WhenAll(...)

标签: c# .net multithreading winforms treeview


【解决方案1】:

这是绝对错误的:

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

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

BeginInvoke() 的实际工作方式如下:

  1. 它在消息循环中发送一些 WM_USER(或类似的)。
  2. 主线程在Application.DoEvents() 中弹出此消息(或在Application.Run() 中始终调用的类似内容)
  3. 主线程执行传递给BeginInvoke()的委托
  4. 主线程表示执行结束(WaitHandle in IAsyncResult
  5. EndInvoke() 等待此类信号(或者如果来自 BeginInvokeIAsyncResult 从未被存储,则会被垃圾回收)

再说一遍:你要么写它纯粹的事件驱动,要么做这样的事情:

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

插件: 要么使用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

【讨论】:

  • Application.DoEvents() 如果您在主线程中做繁重的工作并使用它来获得至少一些响应能力,那么这很糟糕。如果您在其他线程中完成繁重的工作并等待它在这样的 while(!done) Application.DoEvents() 循环中完成,那绝对没问题。 ...但是:通常有一种纯事件方式:)
  • 后台工作结束。新线程(DoWork).Start() void DoWork() { doit();完成=真; ....更好的方法是调用像 BeginInvoke(Finish) 这样的东西并在那里做你想做的事情(在循环之后)
  • 和代码?你可能做错了什么,所以再次:要么使用 while(!done) ... 并且只调用! (没有 BeginInvoke) 总是 BeginInvoke,即使是最后一次完成调用。你选择了哪一个? ....看看我的原始答案和最后一个例子(经过测试!)stackoverflow.com/a/24976017/1722660
  • 创建新的简约项目(就像我在那里为你做的那样)并重现问题。学习如何报告问题(第一个评论您的第一个问题的人告诉您)。我可以修复您的代码,但为什么呢?这样你什么也学不到。首先了解问题,这里有所有信息(你说你的应用程序冻结了,我说永远不要在主线程中使用 Thread.Join() - 我 100% 确定你这样做了)。
  • BeginInvoke() 更好,不用担心,框架程序员很聪明并且使用队列(将所有连续的 BeginInvoke 推送到队列,直到主线程弹出消息并执行所有排队的 deleagets)。跨度>
【解决方案2】:

创建一个Action,其中Invokes 代表,然后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 ,一切都会好起来的。

【讨论】:

  • Invoke() + Thread.Join() = DEADLOCK ;) (后台线程在 Invoke() 中等待,主线程在 Thread.Join() 中锁定,不发送消息)
  • 你完全正确。我认为不再需要那个Join了。
猜你喜欢
  • 2014-03-27
  • 1970-01-01
  • 2016-09-06
  • 1970-01-01
  • 2013-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多