【问题标题】:Event handler seems to finish always after the main thread wait loop事件处理程序似乎总是在主线程等待循环之后完成
【发布时间】:2022-06-11 13:26:04
【问题描述】:

我正在尝试使用SpeechRecognitionEngine.LoadGrammarAsync(grammar) 加载三个语音语法。 加载完成时,异步加载程序会引发 LoadGrammarCompleted 事件。 LoadGrammarCompleted 事件处理程序为这三种语法中的每一种都设置了一个唯一的标志,以表明它已被加载。

在我的主线程中,我想循环并休眠,直到所有语法的加载标志为真。 我希望来自等待循环的调试消息和事件处理程序在加载语法时交错。 但相反,循环总是完成 100 次限制循环,然后突然打印出事件处理程序加载的消息。

发生了什么事?无论我循环等待多长时间,LoadGrammarCompleted 消息总是在记录或打印 100 条循环消息之后打印出来(或记录)。

 // these flags are flipped by the LoadGrammarCompleted event handler
  public static bool
    G2Loaded = false, // false - no grammar has been loaded yet
    G3Loaded = false,
    G1Loaded = false;

// bind the event handler
x.LoadGrammarCompleted += LoadGrammarCompletedHandler;

// load all grammars async
 sre.LoadGrammarAsync(G1);
 sre.LoadGrammarAsync(G2);
 sre.LoadGrammarAsync(G3);
 
 // wait for all the grammars to load
    var grammarsLoaded = G1Loaded && G2Loaded && G3Loaded;
    var limit = 100;
    var count = 0;
    while (! grammarsLoaded && count < limit) {
      Thread.Sleep(20);
      grammarsLoaded = G1Loaded && G2Loaded && G3Loaded;
      count++;
      SendError($Waiting for async grammar loads. {count}");
    }

  // the event handler logs a message when the load operation is completed
  static void
    LoadGrammarCompletedHandler(object? sender, LoadGrammarCompletedEventArgs e) {

    if (e.Grammar.Name == G1_Name) {
      G1Loaded = true;
      SendError($"Grammar is loaded. {e.Grammar.Name}");
    }

The output shows 100 loop messages and THEN the GrammarLoaded event messages. Why?

Output:
Waiting for async grammar loads 1
...
Waiting for async grammar loads 100
Grammar is loaded. helper
Grammar is loaded. sleep

但输出总是显示没有交错,即使语法很小并且与循环等待时间相比可能会立即加载。事件处理程序的输出消息(以及事件处理程序设置的标志)似乎要等到 100 的循环完成。

我做错了什么?主循环线程和事件线程中标志的可见性是否会发生一些事情?我认为也许主线程永远不会看到在事件处理程序中设置的标志的结果。这可以解释为什么主循环总是运行到 100 个循环限制。 (但我认为它不会影响消息输出交错。)

更新:

Hans 给出了明确的答案,表明我的预期是错误的。但它也帮助我了解了我真正的问题应该是什么。

我想并行加载三个语法。这很容易通过连续三次调用sre.LoadGrammarAsync 来完成。这些方法启动进程并“立即”返回到调用线程。

但是现在我如何确定加载何时完成?我在调用LoadGrammarAsync 的同一线程上注册了事件处理程序,因此事件处理程序位于需要等待完成才能继续进行的同一线程上。 Hans 指出,调用线程不能等待(如上图所示使用 Sleep() 循环)并同时在事件处理程序中设置标志。

FWIW,我尝试使用同步 LoadGrammar(x) 方法和 Task.Run,但无法正确加载语法(LoadGrammar 可能不是线程安全的)。

我可以使用什么代码让线程等待异步加载完成?我很茫然。谢谢。

【问题讨论】:

  • 一个线程一次只能做一件事。它可以执行一个while循环,也可以处理一个事件。它不能两者兼得。由于您似乎对 async 或 Thread.Sleep() 没有任何实际用途,因此您不妨使用 LoadGrammer()。
  • 谢谢。你的答案很清楚。当我调用 Thread.Sleep() 时,我的印象是线程去了其他地方,有点像 DoEvents()。但我想不会。我会按照您的建议使用同步加载方法。我可以使用 Task.Run 将它们明确地放在不同的线程上。
  • @HansPassant 我修改了问题以寻求摆脱困境的方法。我想使用LoadGrammarAsync 并行加载这些语法,因为它们有一天会很大。但我不知道该怎么做。

标签: c# events async-await


【解决方案1】:

解决方案是(正如 Hans 间接暗示的那样)在主线程 (t1) 中使用 Task.Run 来调用绑定事件处理程序的方法(在 t2 上)并执行 LoadGrammarAsync 调用以在单独的线程上加载语法(t2)。

然后执行返回到 t1,其中 t1 循环等待由 LoadGrammarCompleted 事件处理程序设置的语法加载标志。

当加载每个语法时触发 LoadGrammarCompleted 事件时,这些事件处理程序在绑定处理程序的线程上运行 (t2)。 t2 事件处理程序可以设置主线程 t1 正在等待的标志。

所以,正如 Hans 所说,t1 线程正在等待和循环,而 t2 线程在加载语法时设置标志。希望有一天这会对某人有所帮助。

【讨论】:

    猜你喜欢
    • 2016-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多