Async / await in .Net & # 8594; Calling a service method that has no asynchronous implementation
Basically, I need to make a remote request using the .Net SDK vendor's SDK to get some information. Their SDK doesn't have asynchronous implementations on their methods, so I'm trying to come up with something myself. I basically want this request to be sent to a synchronous method, and only wait for it for a while. If the request is taking too long, I need to take action and report it to the client in our web application.
I am wondering if this is the best way to do this, or is there a better way? The code below is a service method called from a controller action.
public async Task<bool> SignersAdded(string packageId)
{
var timeout = 5000;
var task = Task.Run(() =>
{
var package = _eslClient.GetPackage(new PackageId(packageId));
return package != null && package.Documents.Values.Any(x => x.Signatures.Any());
});
var stopwatch = Stopwatch.StartNew();
while (!task.IsCompleted)
{
if (stopwatch.ElapsedMilliseconds >= timeout)
return false;
}
return false;
}
source to share
Task.Wait
has an overload that takes on a timeout value int
.
public Task<bool> SignersAdded(string packageId)
{
var timeout = 5000;
var task = Task.Run(() =>
{
var package = _eslClient.GetPackage(new PackageId(packageId));
return package != null && package.Documents.Values.Any(x => x.Signatures.Any());
});
if(!task.Wait(1000 /*timeout*/))
{
// timeout
return false;
}
return task.Result;
}
source to share
Your method doesn't matter await
, so it runs synchronously. In addition, your loop while
will spin the processor, blocking the calling code until the task completes.
The best approach might be the following:
var task = Task.Run(/* your lambda */)
var finishedTask = await Task.WhenAny(Task.Delay(timeout), task);
return finishedTask == task;
Thus, we create a separate delay task during this time, and we await the completion of the first task. This will run truly asynchronously - there is no loop while
that will write processor cycles.
(The above assumes the timeout is milliseconds. If not, use an overload for Delay
instead of an argument TimeSpan
.)
source to share
You're right: run a task that calls GetPackage. After that, you can continue to do other things.
After a while, when you need the result, you can wait for the task to complete. However, you don't need to do Task.Wait. It is much easier to use async / await.
To do this, you need to do three things:
- Declare your async function
- Instead of void return Task and instead of type TResult return Task
<TResult
>. You have already done this. - Instead of waiting for the task to complete, use wait
Your function will look much simpler:
public **async** Task<bool> SignersAdded(string packageId)
{
var timeout = TimeSpan.FromSeconds(5);
var task = Task.Run(() =>
{
var package = _eslClient.GetPackage(new PackageId(packageId));
return package != null
&& package.Documents.Values
.Any(x => x.Signatures.Any());
});
// if desired you can do other things here
// once you need the answer start waiting for it and return the result:
return await Task;
}
if you have a function that returns TResult, the asynchronous version returns Task
<TResult
>.wait return value Task
<TResult
> - TResult
However, if you want to be able to wait with a timeout, you can do the following:
var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(1);
// cancel after 1 second
try
{
return await task.Run( () => ..., tokenSource.Token);
}
catch (OperationCanceledException exc)
{
// handle timeout
}
finally
{
// do necessary cleanup
}
The downside to your async function is that all callers must be asynchronous as well, and all must return Task or Task <TResult
>. There is one exception:
The event handler can be asynchronous, but it can return void
Example:
private async void OnButton1_clicked(object sender, )
source to share
Take a look at the TaskCompletionSource object and the CancellationToken class. Samples here: Timeout for an Asynchronization Method Implemented with a TaskCompletionSource or How to Cancel a TaskCompletion Source Using a Timeout
source to share