您如何等待 AppDomain 在 C# 中处理异步回调,然后 return 结果?

How can you wait on AppDomain to process async callback in C# and then return the results?

我有一些代码可以加载并且 AppDomain(称之为域)调用域内的对象函数。目的是使用设备 API 从 usb 设备获取项目列表以检索信息。 API 需要回调 return 信息。

var AppDomain.CreateDomain(
   $"BiometricsDomain{System.IO.Path.GetRandomFileName()}");
var proxy = domain.CreateInstanceAndUnwrap(proxy.Assembly.FullName, proxy.FullName 
   ?? throw new InvalidOperationException()) as Proxy;
var ids = obj.GetIdentifications();

加载到域中的代理代码如下

public class Proxy : MarshalByRefObject
{ 
    public List<String> GetIdentifications()
    {
        var control = new R100DeviceControl();
        control.OnUserDB += Control_OnUserDB;
        control.Open();
        int nResult = control.DownloadUserDB(out int count);
        // need to be able to return the list here but obviously that is not 
        // going to work.
    }
    private void Control_OnUserDB(List<String> result)
    {
        // Get the list of string from here
    }
}

有什么办法可以在设备上等待,return调用回调时根据需要的信息?由于 GetIdentifications() 已经 returned 我不知道如何获得


注意:虽然对于异步处理的问题可能有更优雅的解决方案,但这种情况发生在子 AppDomain 中这一事实保证了子 AppDomain 的最佳实践。 (见下面的链接)

  1. 不允许在父域中执行用于子 AppDomain 的代码
  2. 不允许复杂类型冒泡到父 AppDomain
  3. 不允许异常以自定义异常类型的形式跨越 AppDomain 边界

OP:

I am using it for fault tolerance

首先,我可能会添加一个 Open 或类似的方法来为数据实现提供时间。

var proxy = domain.CreateInstanceAndUnwrap(proxy.Assembly.FullName, proxy.FullName 
   ?? throw new InvalidOperationException()) as Proxy;

proxy.Open();  // <------ new method here
.
. some time later
.
var ids = obj.GetIdentifications();

然后在您的代理中进行这些更改以允许在后台进行数据处理,以便在您调用 GetNotifications 时数据可能已准备就绪。

public class Proxy : MarshalByRefObject
{ 
    ConcurrentBag<string> _results = new ConcurrentBag<string>();

    public void Open()
    {
        var control = new R100DeviceControl();
        control.OnUserDB += Control_OnUserDB;
        control.Open();
        // you may need to store nResult and count in a field?
        nResult = control.DownloadUserDB(out int count);
    }

    public List<String> GetIdentifications()
    {
        var copy = new List<string>();
        while (_results.TryTake(out var x))
        {
           copy.Add(x);
        }             
        return copy;
    }

    private void Control_OnUserDB(List<String> result)
    {
        // Get the list of string from here
        _results.Add (result);
    }
}

现在,您可能会改进 GetNotifications,以便在数据准备好之前调用 GetNotifications 或在后续数据到达之前调用它时接受超时。

更多

您可以考虑使用 TaskCompletionSource<TResult> 将基于事件的异步模式 (EAP) 操作包装为一项任务,以便可以等待事件。

public class Proxy : MarshalByRefObject {
    public List<String> GetIdentifications() {
        var task = GetIdentificationsAsync();
        return task.Result;
    }
    private Task<List<String>> GetIdentificationsAsync() {
        var tcs = new TaskCompletionSource<List<string>>();
        try {
            var control = new R100DeviceControl();
            Action<List<string>> handler = null;
            handler = result => {
                // Once event raised then set the 
                // Result property on the underlying Task.
                control.OnUserDB -= handler;//optional to unsubscribe from event
                tcs.TrySetResult(result);
            };
            control.OnUserDB += handler;
            control.Open();
            int count = 0;
            //call async event
            int nResult = control.DownloadUserDB(out count);
        } catch (Exception ex) {
            //Bubble the error up to be handled by calling client
            tcs.TrySetException(ex);
        }
        // Return the underlying Task. The client code
        // waits on the Result property, and handles exceptions
        // in the try-catch block there.
        return tcs.Task;
    }
}

您还可以通过添加使用 CancellationToken 取消比预期更长的回调的能力来改进它。

然后可以等待代理

List<string> ids = proxy.GetIdentifications();

引用How to: Wrap EAP Patterns in a Task

不知道你为什么不保持一点状态,然后在调用中等待结果:

public class Proxy : MarshalByRefObject
{ 
    bool runningCommand;
    int lastResult;
    R100DeviceControl DeviceControl { get{ if(deviceControl == null){ deviceControl = new R100DeviceControl(); deviceControl.OnUserDB += Control_OnUserDB; } return deviceControl; } }


    public List<String> GetIdentifications()
    {
        if(runningCommand) return null;
        DeviceControl.Open();
        runningCommand = true;
        lastResult = control.DownloadUserDB(out int count);
    }
    private void Control_OnUserDB(List<String> result)
    {
        runningCommand = false;
        // Get the list of string from here
    }
}

一旦你有了这样的模式,你就可以轻松地在异步和其他模式之间切换,而之前它看起来有点难以理解,因为你集成了异步逻辑,这样你就可以实现同步方法,然后进行异步如果您愿意,可以包装。