如何异步执行同步方法并等待结果

How to execute synchronously method asynchronously and wait for result

我的应用程序通过 TCP/IP 协议和 COM 端口连接到许多外部设备。主要逻辑在MainController class(作为有限状态机实现),它监听来自外部设备的信号并向它们发送命令。

每当 MainController 接收到外部信号时,它会通过事件通知 GUI,例如 OnSensor1ReceivedOnSensor2Received... 这样我就可以显示图标或消息或其他任何内容。相同的逻辑适用于其他方向 - 当 MainController 向外部设备发送信号时,会引发事件并在 GUI 中显示某些内容。

到这里所有的通信都是异步的(如果这是正确的术语?)。这意味着我接收事件并处理它们,然后触发命令,但从不等待同一操作中的 return(您可以说所有发送命令的方法都是无效的)。

但现在我必须添加新设备,它只有一种 public 方法 int Process(string input, out string output)。该方法的执行可能需要几秒钟,同时所有其他代码都在等待 - 例如,如果在执行此方法期间我从另一个外部设备收到信号,则 GUI 将仅在阻塞后刷新方法 Write 执行。如何使 Write 方法异步执行,以便 GUI 实时显示所有其他信号?

代码示例,当我尝试使用新设备时,它在 MainController 某处被调用:

// Notify GUI that the blocking plugin was activated.
OnBlockingPluginActive(this, EventArgs.Empty);

string result;
// Here is the problem - during the execution of this method the GUI is not responding for other signals.
var status = blockingPlugin.Process(input, out result);
if (status == 0)
{
    // OK, notify GUI and fire a BlockingPluginOK command in finite state machine.
    OnBlockingPluginOK(this, EventArgs.Empty);
}
else
{
    // Error, notify GUI and fire a BlockingPluginError command in finite state machine.
    OnBlockingPluginError(this, EventArgs.Empty);
}

另请注意,我使用的是 .net 4.0,无法升级到 4.5,因此不支持 async/await。

您可以通过 BeginInvoke / EndInvoke 将老式的 Asynchronous delegate callAsyncCallback 和捕获的变量一起使用:

// Notify GUI that the blocking plugin was activated.
OnBlockingPluginActive(this, EventArgs.Empty);

string result;
Func<int> processAsync = () => blockingPlugin.Process(input, out result);
processAsync.BeginInvoke(ar =>
{
    var status = processAsync.EndInvoke(ar);
    if (status == 0)
    {
        // OK, notify GUI and fire a BlockingPluginOK command in finite state machine.
        OnBlockingPluginOK(this, EventArgs.Empty);
    }
    else
    {
       // Error, notify GUI and fire a BlockingPluginError command in finite state machine.
       OnBlockingPluginError(this, EventArgs.Empty);
    }
}, null);

Whenever MainController receives an external signal it notifies GUI with an event...

...

Until here all the communication is asynchronous...

它已经是异步的了。它只是使用事件来通知完成而不是 TaskIObservable。普通事件确实会导致更混乱的消费代码(即,您的代码必须分布在许多方法上并显式维护其自己的状态对象),但它们实际上是 异步.

How can I make the Write method execute asynchronously so that the GUI would display all other signals in real time?

好吧,你不能 "make it run asynchronously" - 无论如何,这不是 "asynchronously" 的正确含义。目前,Write 方法会阻塞调用线程(即它是同步的)。没有一种方法可以调用神奇地阻止它阻塞调用线程(即异步)的方法。

但是,您可以使用我称之为 "fake asynchrony" 的技术。本质上,UI 线程 假装 操作是异步的,方法是在线程池线程上执行它。该操作仍在阻塞,但它阻塞了线程池线程而不是 UI 线程。

如果你可以使用Microsoft.Bcl.Async,那么你可以这样写代码:

try
{
  var result = await TaskEx.Run(() =>
  {
    string result;
    if (blockingPlugin.Process(input, out result) != 0)
      throw new Exception("Blocking plugin failed.");
    return result;
  });
  OnBlockingPluginOK(this, EventArgs.Empty);
}
catch
{
  OnBlockingPluginError(this, EventArgs.Empty);
}

否则,你将不得不做老派:

var ui = TaskScheduler.FromCurrentSynchronizationContext();
var task = Task.Factory.StartNew(() =>
{
  string result;
  if (blockingPlugin.Process(input, out result) != 0)
    throw new Exception("Blocking plugin failed.");
  return result;
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
task.ContinueWith(t => OnBlockingPluginOK(this, EventArgs.Empty),
    TaskContinuationOptions.DenyChildAttach | TaskContinuationOptions.OnlyOnRanToCompletion,
    ui);
task.ContinueWith(t => OnBlockingPluginError(this, EventArgs.Empty),
    TaskContinuationOptions.DenyChildAttach | TaskContinuationOptions.NotOnRanToCompletion,
    ui);