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

本文关键字:方法 执行 等待 结果 同步 何异步 异步 | 更新日期: 2023-09-27 17:58:33

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

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

在此之前,所有的通信都是异步的(如果这是正确的术语?)。这意味着我接收事件并处理它们,并发出命令,但从不在同一操作中等待返回(可以说所有发送命令的方法都是无效的)。

但现在我必须添加新的设备,它只有一个公共方法int Process(string input, out string output)。该方法的执行可能需要几秒钟的时间,同时所有其他代码都在等待——例如,如果在执行该方法的过程中,我从另一个外部设备收到信号,则只有在执行阻塞方法Write之后,GUI才会被刷新。如何使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/EndInvokeAsyncCallback和捕获的变量一起使用老式的异步委托调用:

// 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);

每当MainController接收到外部信号时,它就会向GUI通知一个事件。。。

在此之前,所有的通信都是异步的。。。

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

如何使Write方法异步执行,以便GUI实时显示所有其他信号?

好吧,你不能"让它异步运行"——无论如何,这不是"异步"的正确含义。目前,Write方法阻塞调用线程(即,它是同步的)。没有一种方法可以调用该方法,从而神奇地防止它阻塞调用线程(即异步)。

然而,您可以使用一种我称之为"伪异步"的技术。本质上,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);