简体   繁体   中英

C#: How do I raise a child event immediately before the parent event finish executing?

I'm leveraging on a third-party's API to control their hardware. Their methods will never return a value but will only trigger events that will return values that were changed or exceptions occurred while changing the values. I guess it is like UI events.

Let's say there is an event handler button_Click() that I also use to call a function ThirdPartyAPI.SetSomeStuffConfig(some parameters...) besides other functions. After calling that 3rd party function, I need to immediately access the values or exceptions that are only accessible through that function event's event handler. Problem is, the event raised by 3rd party function will only execute after button_Click() has finished.

Is there any solution or coding pattern that I can use to workaround this? Is it possible to trigger and run the third party's event/event handler before the button_Click() 's event handler has finished executing?

Ie, I would like to access the values and exceptions immediately after calling the third party function in button_Click() .

Tried solution

What I have tried is breaking button_Click() into two parts. The parts that occur after calling ThirdPartyAPI.SetSomeStuffConfig(some parameters...) will be put into the 3rd party's event handler. However, this is very troublesome and restrictive.

Sample code of third party's API

public void Stuff_SetConfig(int id, int mode, string Exception)
{

   //returns the new ID, Mode and exception if unable to set ID or Mode on the hardware
    this.ID = id;
    this.Mode = mode;
}

private void button_Click(object sender, EventArgs e)
{
    ThirdParty.Stuff newStuff = new ThirdParty.Stuff();
    newStuff.StuffSetConfig += new ThirdParty.IThirdPartyStuffEvents_StuffSetConfigEventHandler(Stuff_SetConfig);
    //call some functions...

    newStuff.SetConfig(5, 10); //triggers StuffSetConfigEventHandler
    //call other functions...
    Console.WriteLine("ID is " + this.ID + " and this.Mode is " + this.Mode); //ID and Mode is empty because the Stuff_SetConfig event handler has not yet run.
}

(Contingent on the API you're wrapping being similar to EAP).

In the Event-based Async Pattern (EAP), you call an async method and expect events to be raised when the work is completed - whether successfully or unsuccessfully. It's an older pattern for authoring async code in .NET. When Microsoft introduced Task , and especially after they added async and await to the language, there was a lot of interest in adapting the older patterns to make them look like the modern Task -returning methods of the Task-based Async Pattern (TAP).

Unfortunately, transforming EAP isn't as straightforward as adapting the older APM (.NET's original async pattern). However, they do provide guidance on doing so . Their example is the older WebClient class with a DownloadFileAsync method and DownloadFileCompleted event. Here's their adaptation:

 public static Task<string> DownloadStringAsync(Uri url) { var tcs = new TaskCompletionSource<string>(); var wc = new WebClient(); wc.DownloadStringCompleted += (s,e) => { if (e.Error != null) tcs.TrySetException(e.Error); else if (e.Cancelled) tcs.TrySetCanceled(); else tcs.TrySetResult(e.Result); }; wc.DownloadStringAsync(url); return tcs.Task; } 

So, if you can do something similar with the third party API, you can create wrappers and pretend that their methods are Task returning instead. And once you've got that, you can maybe await that Task in the middle of your button_click handler.


Doing it all in one method, we can abuse captures to smuggle the multiple results out, but I'd recommend putting the TaskCompletionSource stuff into a separate method as above and deal with the multiple results in a different way (C# 7 tuples?):

private async void button_Click(object sender, EventArgs e)
{
    ThirdParty.Stuff newStuff = new ThirdParty.Stuff();

    //call some functions...

    var tcs = new TaskCompletionSource<int>();
    int mode;
    newStuff.SetConfig += (_id, _mode, ex) =>
    {
       if(!string.IsNullOrWhitespace(ex))
       {
          tcs.TrySetException(new Exception(ex));
       }
       else
       {
          mode = _mode;
          tcs.TrySetResult(_id);
       }
    }
    newStuff.SetConfig(5, 10);
    //call other functions...
    var id = await tcs.Task;
    Console.WriteLine("ID is " + id + " and this.Mode is " + mode);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM