How should an async callback in a WinRT object be implemented? - c#

So far I could make a delegate type, for example:
// Can't use Task in WinRT interface and TypedEventHandler doesn't work with async await
public delegate IAsyncOperation<string> AsyncEventHandler(object sender, object args);
And then expose in WinRT object like so:
public AsyncEventHandler OnMyEvent { get; set; }
In the WinRT object I would call it like this:
if (OnMyEvent != null)
{
var result = await OnMyEvent.Invoke(this, someArgs);
// Do something with the result...
}
And the client code consuming the WinRT object could do this:
instanceOfWinRTObject.OnMyEvent = OnCalledBackFromWinRT;
But because the delegate returns an IAsyncOperation we need to do some wrapping:
private async Task<string> OnCalledBackFromWinRTAsync(object sender,
object args)
{
return await GetSomeStringAsync(args);
}
private IAsyncOperation<string> OnCalledBackFromWinRT(object sender, object args)
{
return OnCalledBackFromWinRTAsync(sender, args).AsAsyncOperation();
}
It just feels like there must be a cleaner way to achieve this.

Here is an alternative prompted by Peter Torr's comment.
// Custom event args class
public sealed class MyEventArgs
{
public string Result;
public Deferral Deferral;
}
// Declare the event handler on the WinRT component
public event TypedEventHandler<object, MyEventArgs> OnSuspendingEvent;
Then in the WinRT component you could invoke the event like this:
if (OnSuspendingEvent != null)
{
var tcs = new TaskCompletionSource();
using (var deferral = new Deferral(() => tcs.SetResult(true)))
{
var myArgs = MyEventArgs();
myArgs.Deferral = deferral;
OnSuspendUnloading.Invoke(this, myArgs);
await tcs.Task;
DoSomethingWithResult(args.Result);
}
}
And finally in the client code add a handler like so:
instanceOfWinRTObject.OnSuspendingEvent += async (sender, args) =>
{
var deferral = args.Deferral;
try
{
// Client does some async work with a result
args.Result = await GetSomeStringAsync(args);
}
finally
{
deferral.Complete();
}
}

Related

How to start classes on injected dependencies .net Api?

im trying to define two classes, the first one listens a Rabbitmq queue and invoke the event for the second one.
I inject bow classes like scope on my Program.cs (similar for Startup.cs on .net core):
builder.Services.AddScoped<IEventListen<NotificationDTO>, EventListenBase<NotificationDTO>>();
builder.Services.AddScoped<IEventHandler<NotificationDTO>, NotificationEventHandler>();
EventListenBase has the StartListen method and i need when starts the Api, the logic for StartListen begins (his constructor starts), and the logic for event on NotificationEventHandler begins too.
How i do that?
EventListenBase:
public class EventListenBase<T> : IEventListen<T>
where T: class
{
...
public event EventHandler<T> HandleMessage;
public EventListenBase(IEvent<T> #event)
{
...
this.StartListen();
}
public async Task StartListen()
{
var consumer = new AsyncEventingBasicConsumer(_event.GetModel());
consumer.Received += async (ch, ea) =>
{
var body = ea.Body.ToArray();
var text = System.Text.Encoding.UTF8.GetString(body);
var objectValue = JsonConvert.DeserializeObject<T>(text);
//Invoke event to dependencies
HandleMessage?.Invoke(this, objectValue);
...
};
...
}
}
NotificationEventHandler:
public class NotificationEventHandler : IEventHandler<NotificationDTO>
{
public NotificationEventHandler(IEventListen<NotificationDTO> eventHandler)
{
eventHandler.HandleMessage += EventHandler_HandleMessage;
}
private async void EventHandler_HandleMessage(object? sender, NotificationDTO e)
{
await ConcreteHandleMessage(e);
}
public async Task ConcreteHandleMessage(NotificationDTO message)
{
Console.WriteLine("Do something...");
}
}

Using async/await to return the result of a Xamarin.Forms dependency service callback?

I have a Xamarin Forms project and implemented a dependency service to send an SMS but I can't figure out how to convert the device independent callbacks into an async await so that I can return it. For example, with my iOS implementation I have something like:
[assembly: Xamarin.Forms.Dependency(typeof(MySms))]
namespace MyProject.iOS.DS
{
class MySms : IMySms
{
// ...
public void SendSms(string to = null, string message = null)
{
if (MFMessageComposeViewController.CanSendText)
{
MFMessageComposeViewController smsController= new MFMessageComposeViewController();
// ...
smsController.Finished += SmsController_Finished;
}
}
}
private void SmsController_Finished(object sender, MFMessageComposeResultEventArgs e)
{
// Convert e.Result into my smsResult enumeration type
}
}
I can change public void SendSms to public Task<SmsResult> SendSmsAsyc but how do I await for the Finished callback and get it's result so that I can have SendSmsAsync return it?
public interface IMySms
{
Task<bool> SendSms(string to = null, string message = null);
}
public Task<bool> SendSms(string to = null, string message = null)
{
//Create an instance of TaskCompletionSource, which returns the true/false
var tcs = new TaskCompletionSource<bool>();
if (MFMessageComposeViewController.CanSendText)
{
MFMessageComposeViewController smsController = new MFMessageComposeViewController();
// ...Your Code...
//This event will set the result = true if sms is Sent based on the value received into e.Result enumeration
smsController.Finished += (sender, e) =>
{
bool result = e.Result == MessageComposeResult.Sent;
//Set this result into the TaskCompletionSource (tcs) we created above
tcs.SetResult(result);
};
}
else
{
//Device does not support SMS sending so set result = false
tcs.SetResult(false);
}
return tcs.Task;
}
Call it like:
bool smsResult = await DependencyService.Get<IMySms>().SendSms(to: toSmsNumber, message: smsMessage);

There is no active ActorContext, this is most likely due to use of async operations from within this actor

I have a problem, and I am not quite sure how to solve this, except for making my Akka Actor not have async methods.
Here is my Actor Code:
public class AggregatorActor : ActorBase, IWithUnboundedStash
{
public IStash Stash { get; set; }
private AggregatorTimer _aggregatorTimer;
private IActorSystemSettings _settings;
private AccountSummary _accountResponse;
private ContactDetails _contactResponse;
private AnalyticDetails _analyticsResponse;
private FinancialDetails _financialResponse;
private ActorSelection _accountActor;
private ActorSelection _contactActor;
private ActorSelection _analyticsActor;
private ActorSelection _financialActor;
public AggregatorActor(IActorSystemSettings settings) : base(settings)
{
_accountActor = Context.System.ActorSelection(ActorPaths.AccountActorPath);
_contactActor = Context.System.ActorSelection(ActorPaths.ContactActorPath);
_analyticsActor = Context.System.ActorSelection(ActorPaths.AnalyticsActorPath);
_financialActor = Context.System.ActorSelection(ActorPaths.FinancialActorPath);
_settings = settings;
}
#region Public Methods
public override void Listening()
{
ReceiveAsync<ProfilerMessages.ProfilerBase>(async x => await HandleMessageAsync(x));
}
private void Busy()
{
Receive<ProfilerMessages.ProfilerBase>(x => Stash.Stash());
}
private void Aggregate()
{
try
{
Context.Sender.Tell(AggregatedSummaryResponse.Instance(_accountResponse, _contactResponse, _analyticsResponse, _financialResponse));
}
catch (Exception ex)
{
ExceptionHandler(ex);
}
}
public override async Task HandleMessageAsync(object msg)
{
//if is summary, generate new isntance of AggregatorTimer in _eventHandlerCollection.
if (msg is ProfilerMessages.GetSummary)
{
//Become busy. Stash
Become(Busy);
//Handle different requests
var clientId = (msg as ProfilerMessages.GetSummary).ClientId;
await HandleSummaryRequest(clientId);
}
}
private async Task HandleSummaryRequest(string clientId)
{
try
{
var accountMsg = new AccountMessages.GetAggregatedData(clientId);
_accountResponse = (await _accountActor.Ask<Messages.AccountMessages.AccountResponseAll>(accountMsg, TimeSpan.FromSeconds(_settings.NumberOfSecondsToWaitForResponse))).AccountDetails;
//Need to uncomment this
var contactMsg = new ContactMessages.GetAggregatedContactDetails(clientId);
_contactResponse = (await _contactActor.Ask<Messages.ContactMessages.ContactResponse>(contactMsg, TimeSpan.FromSeconds(_settings.NumberOfSecondsToWaitForResponse))).ContactDetails;
var analyticMsg = new AnalyticsMessages.GetAggregatedAnalytics(clientId);
_analyticsResponse = (await _analyticsActor.Ask<Messages.AnalyticsMessages.AnalyticsResponse>(analyticMsg, TimeSpan.FromSeconds(_settings.NumberOfSecondsToWaitForResponse))).AnalyticDetails;
var financialMsg = new FinancialMessages.GetAggregatedFinancialDetails(clientId);
_financialResponse = (await _financialActor.Ask<Messages.FinancialMessages.FinancialResponse>(financialMsg, TimeSpan.FromSeconds(_settings.NumberOfSecondsToWaitForResponse))).FinancialDetails;
//Start new timer
_aggregatorTimer = new AggregatorTimer(_settings.NumberOfSecondsToWaitForResponse);
_aggregatorTimer.TimeElapsed += _aggregatorTimer_TimeElapsed;
}
catch (Exception ex)
{
ExceptionHandler(ex);
}
}
//Event that is raised when an external timers time elapsed.
private async void _aggregatorTimer_TimeElapsed(object sender, ElapsedTimeHandlerArg e)
{
Aggregate();
_aggregatorTimer = null;
_accountResponse = null;
_contactResponse = null;
_analyticsResponse = null;
_financialResponse = null;
//Unstash
Stash.Unstash();
//Start listening again
Become(Listening);
}
#endregion
}
Inside the _aggregatorTimer_TimeElapsed event, I call the await Aggregate function, but the following exception is thrown.
There is no active ActorContext, this is most likely due to use of async operations from within this actor
I think this is caused by the fact that the Aggregate function tries to Tell() the Sender about the responses that are aggregated, but those Tasksare not yet completed? I might be completely wrong, but I have no idea why this is thrown.
I'm not sure what do you even need an AggregatorTimer for - in akka you have a Context.System.Scheduler object, which can be used to schedule events going to happen in the future.
Another thing is that you probably shouldn't execute logic inside event handlers. If you really need them in your code, it's better to limit them only to send a message, once an event gets triggered i.e.:
Receive<TimedOut>(_ => /* handle timeout message */);
var self = Self; // bind current self to a variable, so it won't change
_aggregatorTimer.TimeElapsed += (sender, e) => self.Tell(new TimedOut(), self);

Control flow in asynchronous calls

I'm facing difficulties understanding how to handle program control during asynchronous flow.
I have a SessionManager class which calls the initiates the session and we need to register
for the event OnStartApplicationSessionResponse and my control will return to the calling point. I will get the session id in the eventhandler after sometime or the error code if there is an error.
class SessionManager
{
public bool startUp(Object params)
{
try
{
serviceProvider = new ServiceProvider();
serviceProvider.OnStartApplicationSessionResponse += new StartApplicationSessionResponseHandler(ServiceProvider_OnStartApplicationSessionResponse);
serviceProvider.startUp(params);
}
}
public void ServiceProvider_OnStartApplicationSessionResponse(object sender, ServiceProvider.StartApplicationSessionResponseArgs e)
{
//e.getError
//I will get the session Id here or error code
}
}
How do I get sessionId or the error as my control is now at the calling position?
You could use TaskCompletionSource to make the Event awaitable.
class SessionManager
{
private ServiceProvider _serviceProvider;
public int SessionId
{
get;
private set;
}
public Task<bool> StartUp(Object param)
{
_serviceProvider = new ServiceProvider();
var tcs = new TaskCompletionSource<bool>();
_serviceProvider.OnStartApplicationSessionResponse += (sender, args) =>
{
// do your stuff
// e.g.
SessionId = 0xB00B5;
tcs.SetResult(true);
};
_serviceProvider.startUp(param);
return tcs.Task;
}
}
The call would look like:
private static async void SomeButtonClick()
{
var mgr = new SessionManager();
var success = await mgr.StartUp("string");
if (success)
{
Console.WriteLine(mgr.SessionId);
// update ui or whatever
}
}
note: This Feature is available in .Net 4.5.
With the C# feature async and await you are able to rewrite an asynchronous flow into something that is like a synchronous flow. You have only provided some fragments of your code so to provide a complete example I have created some code that resembles your code:
class StartEventArgs : EventArgs {
public StartEventArgs(Int32 sessionId, Int32 errorCode) {
SessionId = sessionId;
ErrorCode = errorCode;
}
public Int32 SessionId { get; private set; }
public Int32 ErrorCode { get; private set; }
}
delegate void StartEventHandler(Object sender, StartEventArgs e);
class ServiceProvider {
public event StartEventHandler Start;
public void Startup(Boolean succeed) {
Thread.Sleep(TimeSpan.FromSeconds(1));
if (succeed)
OnStart(new StartEventArgs(321, 0));
else
OnStart(new StartEventArgs(0, 123));
}
protected void OnStart(StartEventArgs e) {
var handler = Start;
if (handler != null)
handler(this, e);
}
}
The ServiceProvider.Startup method will delay for a second before firing an event that either signals success or failure depending on the succeed parameter provided. The method is rather silly but hopefully is similar to the behavior of your ServiceProvider.Startup method.
You can convert the asynchronous startup into a task using a TaskCompletionSource:
Task<Int32> PerformStartup(ServiceProvider serviceProvider, Boolean succeed) {
var taskCompletionSource = new TaskCompletionSource<Int32>();
serviceProvider.Start += (sender, e) => {
if (e.ErrorCode > 0)
throw new Exception(e.ErrorCode.ToString());
taskCompletionSource.SetResult(e.SessionId);
};
serviceProvider.Startup(succeed);
return taskCompletionSource.Task;
}
Notice how an error signaled by the Start event is converted into an Exception (in production code you should use a custom exception type instead).
Using the async and await feature of C# you can now write code that looks very much like synchronous code even though it actually is asynchronous:
async void Startup(Boolean succeed) {
var serviceProvider = new ServiceProvider();
try {
var sessionId = await PerformStartup(serviceProvider, succeed);
Console.WriteLine(sessionId);
}
catch (Exception ex) {
Console.WriteLine(ex);
}
}
If an error is reported by the Start event you can now deal with in the catch block. Also the session ID is simply a return value of the function. The "magic" is that using await on a Task will return the result of the task when it completes and if an exception is thrown in the task it can be caught on the thread awaiting the task.

BackgroundTask deferral.Complete

My question is, what does the deferral.complete() method exactly, does this method call the event task.Compledet, or is there a way to call a method from the BackgroundTaskSyncer in my class BackgroundSyncer ????
When i run the Programm i will do the Run method from BackgroundTaskSyncer but nothing in the other class??
namespace NotificationTask
{
public sealed class BackgroundTaskSyncer : IBackgroundTask
{
public void Run(IBackgroundTaskInstance taskInstance)
{
BackgroundTaskDeferral deferral = taskInstance.GetDeferral();
deferral.Complete();
}
}
}
namespace Services
{
public static class BackgroundSync
{
private static async Task RegisterBackgroundTask()
{
try
{
BackgroundAccessStatus status = await BackgroundExecutionManager.RequestAccessAsync();
if (status == BackgroundAccessStatus.AllowedWithAlwaysOnRealTimeConnectivity || status == BackgroundAccessStatus.AllowedMayUseActiveRealTimeConnectivity)
{
bool isRegistered = BackgroundTaskRegistration.AllTasks.Any(x => x.Value.Name == "Notification task");
if (!isRegistered)
{
BackgroundTaskBuilder builder = new BackgroundTaskBuilder
{
Name = "Notification task",
TaskEntryPoint =
"NotificationTask.BackgroundTaskSyncer"
};
builder.SetTrigger(new TimeTrigger(15, false));
builder.AddCondition(new SystemCondition(SystemConditionType.InternetAvailable));
BackgroundTaskRegistration task = builder.Register();
task.Completed += new BackgroundTaskCompletedEventHandler(OnCompleted);
task.Progress += new BackgroundTaskProgressEventHandler(OnProgress);
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("The access has already been granted");
}
}
private static void OnCompleted(IBackgroundTaskRegistration task, BackgroundTaskCompletedEventArgs args)
{
ToTheBackGroundWork();
}
Deferrals were created to work around a problem with async void events and methods. For example, if you had to await during a background operation, you would use an async void Run method. But the problem with that is that the runtime has no idea that you actually have more work you want to do.
So, a deferral is an object that you can use to inform the runtime "I'm really done now." A deferral is only necessary if you need to await.
I have a blog post that goes into "asynchronous event handlers" and deferrals in more detail.

Categories