Get the current Task instance in an async method body - c#

If I have an async method body like so -
public async Task GetSomething() {
await SendText("hi");
await SendImage("bla.bmp");
}
How can I get the Task object before it is returned to the user when the await kicks in
ie..
public async Task GetSomething() {
myTasks.Add(Task.Current);
await SendText("hi");
await SendImage("bla.bmp");
//some processing
}
so that somewhere else I can do
await Task.WhenAll(myTasks);
Console.WriteLine("All tasks complete");
This is so that I can wait for all tasks to complete before shutting down

This is not directly possible as the language does not have a facility to access the "current" task.
There is a workaround though: Wrap your async method in another method. This other method can get hold of the task once the async method returns (which happens approximately at the first await point).
In all cases I recommend letting the caller add the async Task to your list, not the async method itself. This is useful even from an encapsulation point of view.

Related

How can I call an async method within a sync method?

I'm trying to call an async task (SIn) within a synch method (SignIn). I need the synch method because I'm passing ref to that method. But when I'm calling the async task, the GUI is frozen. The async task is a simple login with the onedrive sdk.
I've tried to waited the task, but the GUI still frozen. I've also tried creating a new Thread, but it didn't work too. How can I call the async method?
public override bool SignIn(ref User user)
{
try
{
signInEnd = false;
signinUser = user;
Task<bool> task = SIn();
task.Wait();
return task.Result;
}
catch(Exception e)
{
return false;
}
}
public async Task<bool> SIn()
{
var msaAuthProvider = new MsaAuthenticationProvider(
this.oneDriveClientId,
this.oneDriveReturnUrl,
this.scopes,
new CredentialVault(this.oneDriveClientId));
await msaAuthProvider.AuthenticateUserAsync();
driveClient = new OneDriveClient(this.oneDriveBaseUrl, msaAuthProvider);
}
Calling Wait() blocks the UI thread which means that the continuation of SIn(), i.e. the part that will eventually be executed once the Task returned by AuthenticateUserAsync() has completed, won't be able to execute on this thread. This results in a deadlock.
You may be able to get around this by avoiding capturing the context by calling ConfigureAwait(false) in SIn():
public async Task<bool> SIn()
{
var msaAuthProvider = new MsaAuthenticationProvider(
this.oneDriveClientId,
this.oneDriveReturnUrl,
this.scopes,
new CredentialVault(this.oneDriveClientId));
await msaAuthProvider.AuthenticateUserAsync().ConfigureAwait(false);
driveClient = new OneDriveClient(this.oneDriveBaseUrl, msaAuthProvider);
}
But the "real" solution to this kind of issues is not to mix asynchronous and synchronous code, i.e. SignIn should be asynchronous and await SIn(). Don't block on asynchronous code by calling Wait() or Result:
public Task<bool> SignIn(User user)
{
try
{
...
return await SIn();
}
catch (Exception e)
{
return false;
}
}
Please refer to #Stephen Cleary's blog post for more information about this.
mm8 is right that not calling async from inside a sync method is the best way to solve your issue,
remember that the public async void EventHandler() method was specifically designed for running long running tasks from a gui linked control
However it isn't always possible to rewrite an entire system to be async when only one small section needs changing
In this case you should avoid waiting for the results as this makes the async process pointless, what you can do though is break your synchronous code into 2 parts a before and after
the before method will prep and launch the task,
the after handles the results
ie
public async Task<string> GetData(int delay)
{
await Task.Delay(delay);
return "complete";
}
public void StartGettingData()
{
GetData(5000).ContinueWith(t => CompleteGetData(t.Result), TaskScheduler.Current);
}
public void CompleteGetData(string message)
{
UpdateStatus(message);
}
this method does have the added complexity of requiring you to ensure thread safety yourself, which is why the async/await functionality was introduced

Should async or non-async methods be used in SignalR Hub?

I have the following Hub class in SignalR part and here I defined all of the methods related to connection:
public override Task OnConnected()
{
// here I cannot call this, and need to convert this method async
await AddToGroup("stockGroup");
//
string name = Context.User.Identity.Name;
_connections.Add(name, Context.ConnectionId);
return base.OnConnected();
}
public async Task AddToGroup(string groupName)
{
await Groups.Add(Context.ConnectionId, groupName);
await Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId} joined");
}
I have a look at many different examples regarding to these connection methods (and other Hub methods) and see that some of them use async method while some of them not. In this example above, I need to convert OnConnected() method to async in order to call AddToGroup() method. Of course the reverse situation would also be ok, but I am not sure which one is better. So, should I use async methods or non-async methods for all of the methods in the Hub? Any help would be appreciated.
Update 1 : Converted method (to async).
public override async Task OnConnected()
{
await AddToGroup("stockGroup");
string name = Context.User.Identity.Name;
_connections.Add(name, Context.ConnectionId);
return base.OnConnected();
}
Update 2:
public override async Task OnConnected()
{
// #1 There is no async method in "Microsoft.AspNet.SignalR" library.
//await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
// #2 I just have sync version of "AddToGroupAsync()" and used it
await Groups.Add(Context.ConnectionId, "SignalR Users");
/* #3 I think there is no need to use this custom method in the Hub.
Because the same method is already exist in the IGroupManager interface */
//await AddToGroup("jiraGroup");
string name = Context.User.Identity.Name;
_connections.Add(name, Context.ConnectionId);
// #4 Here also the same problem and I used sync version of OnConnected()
//await base.OnConnectedAsync();
await base.OnConnected();
}
Just add async if you need await inside method. async is a marker for compiler to generate async state machine.
should I use async- use if you need to await.
If you don't need to await just return task itself without async. In that case you can avoid creation of state machine.
Also please read Async/Await FAQ from Stephen Toub:
When you mark a method with the “async” keyword, you’re really telling
the compiler two things:
You’re telling the compiler that you want to be able to use the
“await” keyword inside the method (you can use the await keyword if
and only if the method or lambda it’s in is marked as async). In doing
so, you’re telling the compiler to compile the method using a state
machine, such that the method will be able to suspend and then resume
asynchronously at await points.
You’re telling the compiler to “lift”
the result of the method or any exceptions that may occur into the
return type. For a method that returns Task or Task, this
means that any returned value or exception that goes unhandled within
the method is stored into the result task. For a method that returns
void, this means that any exceptions are propagated to the caller’s
context via whatever “SynchronizationContext” was current at the time
of the method’s initial invocation.
You should use Async methods with await and return Task.
Asynchrony is viral so you should avoid async void.
Bad:
public async void MethodAsync()
{
var result = await SendAsync();
DoSomething(result);
}
Good:
public async Task MethodAsync()
{
var result = await SendAsync();
DoSomething(result);
}
There are some awesome async guidelines by #davidfowl here.
Update: remove the return:
public override async Task OnConnected()
{
await AddToGroup("stockGroup");
string name = Context.User.Identity.Name;
_connections.Add(name, Context.ConnectionId);
await base.OnConnected();
}

async method lacks await operators message when I call another async method from it?

My OnAppearing message is async and from that I call another async method which has some code that modifies the UI. I then do some work and finally modify the UI again.
Here's the code. However I am getting a warning message that confuses me. I added the warning code below.
protected async override void OnAppearing()
{
base.OnAppearing();
// actions
CreateListSectionAsync();
}
public async void CreateListSectionAsync()
{
// modify UI
// do some work that takes time
await Task.Run(() =>
{
Device.BeginInvokeOnMainThread(() =>
{
// modify UI
});
});
}
ListPage.xaml.cs(39,39): Warning CS1998: This async method lacks
'await' operators and will run synchronously. Consider using the
'await' operator to await non-blocking API calls, or 'await
Task.Run(...)' to do CPU-bound work on a background thread. (CS1998)
Why am I getting this warning and how can I fix it?
await CreateListSectionAsync();
Change the signature of the fuction to return a Task not void
public async Task CreateListSectionAsync()

Compose async methods with await? [duplicate]

This question already has an answer here:
eliding async and await in async methods [duplicate]
(1 answer)
Closed 4 years ago.
When passing through method calls to another async method, should the caller method also be async and use await, or should I simply pass through the Task it receives from the callee? What if the calling method performs a bit more preparation?
public Task<Message> Unsubscribe(int subscriptionId, CancellationToken cancellationToken)
{
var data = new MessageData
{
["subscriptionId"] = subscriptionId
};
return SendAsync(OpCode.Unsubscribe, data, cancellationToken);
}
public Task<Message> Unsubscribe(int subscriptionId) =>
Unsubscribe(subscriptionId, CancellationToken.None);
SendAsync is async and returns Task<Message>. So should the first overload of Unsubscribe be like above or like that:
public async Task<Message> Unsubscribe(int subscriptionId, CancellationToken cancellationToken)
{
var data = new MessageData
{
["subscriptionId"] = subscriptionId
};
return await SendAsync(OpCode.Unsubscribe, data, cancellationToken);
}
The other alternative is with the second overload of Unsubscribe. It might be like above or like that:
public async Task<Message> Unsubscribe(int subscriptionId) =>
await Unsubscribe(subscriptionId, CancellationToken.None);
I guess that more asyncs and awaits add complexity introduced by the compiler (I see it in the stack traces!) and may degrade performance and memory consumption. But at least it should provide for a consistent exception propagation.
In the examples you cited, just returning the task without awaiting it is fine (and arguably preferable), but this does require some care.
Once case were you can get into trouble is when you're dealing with Tasks inside a using block. These can have vastly different behaviors:
public async Task<Something> AwaitTheTask()
{
using (var someResource = GetAResource())
{
return await SomeAsyncThing(someResource);
}
}
public Task<Something> DontAwaitTheTask()
{
using (var someResource = GetAResource())
{
return SomeAsyncThing(someResource);
}
}
In the first example, the using block will not dispose someResource until the awaited Task has completed. In the second example, someResource will be disposed right away, very likely causing problems for the code that needs that resource.

Correct way of calling await inside SignalR 2.0 OnReceived handler

I'm using SignalR 2.0.1 with PersistentConnection (not hubs) and currently my default very simple OnReceived handler looks like this:
protected override Task OnReceived(IRequest request, string connectionId, string data)
{
return Connection.Broadcast(data);
}
I want to add some I/O-bound code to this handler, like accessing a DB on a different machine. Naturally I want everything to be asynchronous and I don't want to block the thread so I'm hoping to use an async flavor like db.SaveChangesAsync() of EF6 (instead of the regular blocking db.SaveChanges).
I added the db.SaveChangesAsync() to the handler, but I need to await on it too. So I've also added an async modifier to the handler, but this caused an error with my return value - I can't return Connection.Broadcast(data) anymore.
This is what I got eventually:
protected override async Task OnReceived(IRequest request, string connectionId, string data)
{
// some EF code here..
await db.SaveChangesAsync();
// the original return changes into this?
await Connection.Broadcast(data);
}
Is this the correct way to do this? Because I have a feeling I'm abusing the pattern.
btw, if I understand correctly, the current version of SignalR is fully asynchronous. Older versions like the one described here had two handlers - one synchronous and one asynchronous (with the Async postfix).
Yes, that is a perfectly reasonable way of going about it. Why do you feel that you may be abusing the pattern?
Think of it this way:
a synchronous method returning void corresponds to an async method returning Task. Likewise,
a synchronous method returning T corresponds to an async method returning Task<T>.
That is why you cannot do
protected override async Task OnReceived(IRequest request, string connectionId, string data)
{
return Connection.Broadcast(data);
}
, since the async keyword and your return statement would indicate a method that returned Task<Task>.
What you can do is remove your last await altogether. All it will do is create an empty continuation (because it is essentially saying "when the broadcast is done, run the code after the broadcast and until the ending curly brace"). Or you can leave it in for consistency, if you prefer.
I have an async intro on my blog that you may find helpful.
A Task instance represents a "future". So when you're doing this:
protected override Task OnReceived(IRequest request, string connectionId, string data)
{
return Connection.Broadcast(data);
}
You're saying "OnReceived is done when Connection.Broadcast(data) is done". This is practically the same as:
protected override async Task OnReceived(IRequest request, string connectionId, string data)
{
await Connection.Broadcast(data);
}
Which is saying "OnReceived will (asynchronously) wait for Connection.Broadcast(data) to be done, and then OnReceived will be done." It's slightly more efficient without the async and await, but they have practically the same semantics.
So, yes, this code would be correct:
protected override async Task OnReceived(IRequest request, string connectionId, string data)
{
// some EF code here..
await db.SaveChangesAsync();
await Connection.Broadcast(data);
}

Categories

Resources