jwize
jwize

Reputation: 4165

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

I have some code that loads up and AppDomain(call it domain) calling an object function within the domain. The purpose is to get a list of items from a usb device using the device API to retrieve the information. The API requires a callback to return the information.

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

The proxy code loaded into the domain is as follows

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
    }
}

Is there a way to be able to wait on the device and return the information as needed when the callback is called? Since the GetIdentifications() has already returned I don't know how to get the

Upvotes: 1

Views: 567

Answers (3)

user585968
user585968

Reputation:


NOTE: Though there may be more elegant solutions to the problem of asynchronous processing, the fact that this occurs in a child AppDomain warrants child AppDomain best practices. (see links below)

i.e.

  1. do not allow code meant for a child AppDomain to be executed in the parent domain
  2. do not allow complex types to bubble to the parent AppDomain
  3. do not allow exceptions to cross AppDomain boundaries in the form of custom exception types

OP:

I am using it for fault tolerance

First I would probably add a Open or similar method to give time for the data to materialise.

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

Then in your proxy make these changes to allow for data processing to occur in the background so that by the time you call GetNotifications data may be ready.

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

Now you could probably improve upon GetNotifications to accept a timeout in the event either GetNotifications is called before data is ready or if you call it multiply but before subsequent data to arrive.

More

Upvotes: 2

Nkosi
Nkosi

Reputation: 247323

You can consider wrapping the Event-Based Asynchronous Pattern (EAP) operations as one task by using a TaskCompletionSource<TResult> so that the event can be awaited.

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;
    }
}

You can also improve on it by adding the ability to cancel using a CancellationToken for longer than expected callbacks.

With that the proxy can then be awaited

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

Reference How to: Wrap EAP Patterns in a Task

Upvotes: 2

Jay
Jay

Reputation: 3355

Not sure why you just don't maintain a little state and then wait for the results in the call:

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
    }
}

Once you have a pattern like this you can easily switch between async and otherwise whereas before it will look a little harder to understand because you integrated the async logic, this way you can implement the sync method and then make an async wrapper if you desire.

Upvotes: 0

Related Questions