When converting an existing synchronous method to async, I accidentally used "async void" on one of the methods, which resulted in some unexpected behavior.
Below is a simplified example of the kind of change I had actually performed,
public IActionResult Index()
{
var vm = new ViewModel();
try
{
var max = 0;
if (_dbContext.OnlyTable.Any())
{
max = _dbContext.OnlyTable.Max(x => x.SomeColumn);
}
_dbContext.Add(new TestTable() { SomeColumn = max + 1 });
_dbContext.SaveChanges();
MakePostCallAsync("http:\\google.com", vm);
if (!string.IsNullOrEmpty(vm.TextToDisplay))
{
vm.TextToDisplay = "I have inserted the value " + newmax + " into table (-1 means error)";
}
else
{
vm.TextToDisplay = "Errored!";
}
}
catch (Exception ex)
{
vm.TextToDisplay = "I encountered error message - \"" + ex.Message + "\"";
}
return View("Index", vm);
}
private async void MakePostCallAsync(string url, ViewModel vm)
{
var httpClient = new HttpClient();
var httpResponse = await httpClient.PostAsync("http://google.com", null).ConfigureAwait(true);
newmax = _dbContext.OnlyTable.Max(x => x.SomeColumn);
}
The issue is that, the MakePostCallAsync() method, when trying to query the database using DbContext, throws an exception saying DbContext is already disposed.
_dbContext in the example is injected using ASP .Net Core's DI (through AddDbContext() extension) with its default scope (Scoped).
I fail to and need help understand the following,
why the DB context is disposed even before the request is served, while Scoped object's lifetime should be the entire duration of the current request
Even though I have used ConfigureAwait(true) (which explicitly means that once the awaited method returns MakePostCallAsync() should continue in Request context?) - this doesn't seem to be happening
In the actual code, this was fixed once I made all the methods async (all the way up to controller) - in the repro I have shared, even that doesn't help prevent the exception - why is it so, how do I solve this?
Repro is available in https://github.com/jjkcharles/SampleAsync
You should never use async void unless you are writing a event handler. If you want to use async/await you need to go all the way up the call stack till you get to return a Task<IActionResult>
public async Task<IActionResult> Index()
{
var vm = new ViewModel();
try
{
var max = 0;
if (_dbContext.OnlyTable.Any())
{
max = _dbContext.OnlyTable.Max(x => x.SomeColumn);
}
_dbContext.Add(new TestTable() { SomeColumn = max + 1 });
_dbContext.SaveChanges();
await MakePostCallAsync("http:\\google.com", vm);
if (!string.IsNullOrEmpty(vm.TextToDisplay))
{
vm.TextToDisplay = "I have inserted the value " + newmax + " into table (-1 means error)";
}
else
{
vm.TextToDisplay = "Errored!";
}
}
catch (Exception ex)
{
vm.TextToDisplay = "I encountered error message - \"" + ex.Message + "\"";
}
return View("Index", vm);
}
private async Task MakePostCallAsync(string url, ViewModel vm)
{
var httpClient = new HttpClient();
var httpResponse = await httpClient.PostAsync("http://google.com", null).ConfigureAwait(true);
newmax = _dbContext.OnlyTable.Max(x => x.SomeColumn);
}
It seems to me you have some trouble understanding how to use async-await, because I see several major errors in your program.
This article, written by the ever so helpful Stephen Cleary helped me to understand how to use it properly.
Every function that wants to use async-await has to return Task instead of void and Task<TResult> instead of TResult. The only exception to this rule are event handlers that are not interested in the result of the actions.
So first change Index such that it returns a Task<IActionResult>. Once you've done this, your compiler will probably warn you that you forgot to await somewhere inside your Index function.
If you call an async function, you don't have to wait for it to finish, you can do other useful stuff, that will be performed whenever the async function has to await for something. But before you can use any result of the async function you have to await until it is ready:
var taskMakePostCall = MakePostCallAsync(...)
// if you have something useful to do, don't await
DoSomethingUseful(...);
// now you need the result of MakePostCallAsync, await until it is finished
await taskMakePostCall;
// now you can use the results.
If your MakePostCallAsync would have returned something, for instance an int, the return value would have been Task<int> and your code would have been:
Task<int> taskMakePostCall = MakePostCallAsync(...)
DoSomethingUseful(...);
int result = await taskMakePostCall;
If you don't have something useful to do, just await immediately:
int result = await MakePostCallAsync(...);
The reason for your exception is that your MakePostCallAsync is not finished completely before you somewhere Dispose your dbContext, probably via a using statement. After adding this await before returning you are certain that MakePostCallAsync is completely finished before returning Index()
In your example you are not awaiting your call to MakePostCallAsync("http:\\google.com", vm). This means the request continues execution immediately and it eventually executes the code that disposes of your _dbContext, presumably while MakePostCallAsync is still waiting for the HTTP client to return a response. Once the HTTP client does return a response your MakePostCallAsync tries to call newmax = _dbContext.OnlyTable.Max(x => x.SomeColumn) but your request has already been handled and your DB context disposed of by then.
Related
I have an async method that makes an API call to a vendor. The vendor has requested that in the event of an error, we make an additional 2 calls with exactly the same information in the calls. My first thought for this scenario is to make my API method recursive. However, after looking at many questions on this site and other articles, I'm having a hard time grasping how async/await works with recursion.
My method (simplified for demonstration) follows. I'm not sure if I should be awaiting the recurisve call? Visual Studio throws the standard Because this call is not awaited, execution will continue...etc if I don't await the recursive call. It does appear to work when I do await the recursive call, I'm just afraid of what I'm doing to stack if I've done this incorrectly. Any help would be appreciated.
public async Task<string> GetData(int UserID, int Retry = 0)
{
try
{
var Request = new HttpRequestMessage(HttpMethod.Post, "myurlhere");
//Set other request info like headers and payload
var Response = await WebClient.SendAsync(Request);
return await Response.Content.ReadAsStringAsync();
}
catch (Exception Ex)
{
if (Retry <= 2)
{
Retry++;
return await GetData(UserID, Retry); //Should I await this?
}
else
{
return "";
}
}
}
It does appear to work when I do await the recursive call,
Grand, so.
I'm just afraid of what I'm doing to stack if I've done this incorrectly.
Because awaiting Tasks that don't return synchronously is handled by a state machine, it is in fact less burden on the stack in such a case than if it was "normal" non-async code. The recursive call becomes another state machine and a reference to it is a field in the first state machine.
It's possible to take an iterative rather than recursive approach to this generally (have a loop and exit on first success) but really since even the non-async equivalent wouldn't have significant stack pressure there's no need to do things any differently than you have done.
This is not a situation where recursion is needed. As others have suggested I would use something like this:
public async Task<string> GetDataWithRetry(int UserID, int Tries = 1)
{
Exception lastexception = null;
for (int trycount=0; trycount < tries; trycount++)
try
{
return await GetData(UserID);
}
catch (Exception Ex)
{
lastexception = Ex;
}
throw lastexception;
}
public async Task<string> GetData(int UserID)
{
var Request = new HttpRequestMessage(HttpMethod.Post, "myurlhere");
//Set other request info like headers and payload
var Response = await WebClient.SendAsync(Request);
return await Response.Content.ReadAsStringAsync();
}
I've been reading examples for a long time now, but unfortunately I've been unable to apply the solutions to the code I'm working with. Some quick Facts/Assorted Info:
1) I'm new to C#
2) The code posted below is modified from Amazon Web Services (mostly stock)
3) Purpose of code is to compare server info to offline already downloaded info and create a list of need to download files. This snip is for the list made from the server side, only option with AWS is to call async, but I need this to finish before moving forward.
public void InitiateSearch()
{
UnityInitializer.AttachToGameObject(this.gameObject);
//these are the access key and secret access key for credentials
BasicAWSCredentials credentials = new BasicAWSCredentials("secret key", "very secret key");
AmazonS3Config S3Config = new AmazonS3Config()
{
ServiceURL = ("url"),
RegionEndpoint = RegionEndpoint.blahblah
};
//Setting the client to be used in the call below
AmazonS3Client Client = new AmazonS3Client(credentials, S3Config);
var request = new ListObjectsRequest()
{
BucketName = "thebucket"
};
Client.ListObjectsAsync(request, (responseObject) =>
{
if (responseObject.Exception == null)
{
responseObject.Response.S3Objects.ForEach((o) =>
{
int StartCut = o.Key.IndexOf(SearchType) - 11;
if (SearchType == o.Key.Substring(o.Key.IndexOf(SearchType), SearchType.Length))
{
if (ZipCode == o.Key.Substring(StartCut + 12 + SearchType.Length, 5))
{
AWSFileList.Add(o.Key + ", " + o.LastModified);
}
}
}
);
}
else
{
Debug.Log(responseObject.Exception);
}
});
}
I have no idea how to apply await to the Client.ListObjectsAsync line, I'm hoping you all can give me some guidance and let me keep my hair for a few more years.
You can either mark your method async and await it, or you can call .Wait() or .Result() on the Task you're given back.
I have no idea how to apply await to the Client.ListObjectsAsync line
You probably just put await in front of it:
await Client.ListObjectsAsync(request, (responseObject) => ...
As soon as you do this, Visual Studio will give you an error. Take a good look at the error message, because it tells you exactly what to do next (mark InitiateSearch with async and change its return type to Task):
public async Task InitiateSearchAsync()
(it's also a good idea to add an Async suffix to follow the common pattern).
Next, you'd add an await everywhere that InitiateSearchAsync is called, and so on.
I'm assuming Client.ListObjectsAsync returns a Task object, so a solution for your specific problem would be this:
public async void InitiateSearch()
{
//code
var collection = await Client.ListObjectsAsync(request, (responseObject) =>
{
//code
});
foreach (var item in collection)
{
//do stuff with item
}
}
the variable result will now be filled with the objects. You may want to set the return type of InitiateSearch() to Task, so you can await it too.
await InitiateSearch(); //like this
If this method is an event handler of some sort (like called by the click of a button), then you can keep using void as return type.
A simple introduction from an unpublished part of the documentation for async-await:
Three things are needed to use async-await:
The Task object: This object is returned by a method which is executed asynchronous. It allows you to control the execution of the method.
The await keyword: "Awaits" a Task. Put this keyword before the Task to asynchronously wait for it to finish
The async keyword: All methods which use the await keyword have to be marked as async
A small example which demonstrates the usage of this keywords
public async Task DoStuffAsync()
{
var result = await DownloadFromWebpageAsync(); //calls method and waits till execution finished
var task = WriteTextAsync(#"temp.txt", result); //starts saving the string to a file, continues execution right await
Debug.Write("this is executed parallel with WriteTextAsync!"); //executed parallel with WriteTextAsync!
await task; //wait for WriteTextAsync to finish execution
}
private async Task<string> DownloadFromWebpageAsync()
{
using (var client = new WebClient())
{
return await client.DownloadStringTaskAsync(new Uri("http://stackoverflow.com"));
}
}
private async Task WriteTextAsync(string filePath, string text)
{
byte[] encodedText = Encoding.Unicode.GetBytes(text);
using (FileStream sourceStream = new FileStream(filePath, FileMode.Append))
{
await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
}
}
Some thing to note:
You can specify a return value from an asynchronous operations with Task. The await keyword waits till the execution of the method finishes, and returns the string.
the Task object contains the status of the execution of the method, it can be used as any other variable.
if an exception is thrown (for example by the WebClient) it bubbles up at the first time the await keyword is used (in this example at the line string result (...))
It is recommended to name methods which return the Task object as MethodNameAsync
For more information about this take a look at http://blog.stephencleary.com/2012/02/async-and-await.html.
I am building an app that will have registration option and I've made a method for checking whether username is available or not that is async. Method connects via WebRequest to my PHP/MySQL API and retrieves if username is taken or not.
However whenever I try to run this the code blocks on line
var response = await webRequest.GetResponseAsync();
and whole application just freezes. Doing this same method synchronously works fine though.
Whole method:
async public Task<UsernameAvailable> usernameAvailable()
{
try
{
token = "token=single";
function = "&function=checkUser";
string param = "¶m=" + User.username;
string result = "";
var webRequest = WebRequest.Create(#"http://" + APIURL + token + function + param);
var response = await webRequest.GetResponseAsync(); //troubled line
var content = response.GetResponseStream();
StreamReader reader = new StreamReader(content);
result = reader.ReadLine();
if (result == "0")
{
state = "Username is available.";
return UsernameAvailable.Available;
}
else
{
state = "Username is not available.";
return UsernameAvailable.NotAvailable;
}
}
catch
{
state = "Error checking for username.";
return UsernameAvailable.Error;
}
}
I'm going to go on a wild guess here and say that higher up the callstack you're calling usernameAvailable like this:
usernameAvailable().Result
Which is blocking on async code, causing your app to deadlock. This is why you shouldn't block on async code. The reason your answer works is because using ConfigureAwait(false) prevents the synchronization context from following to the continuation, but that is just a dirty workaround which covers up the actual problem of your code.
Instead of doing that, you should also use an async event handler and await on your method as well:
public async void SomeEventHandler(object sender, EventArgs e)
{
await usernameAvailable();
}
Side note - Async methods should have the Async postfix added to them, so your method should actually be named UsernameAvaliableAsync()
Adding .ConfigureAwait(false) to the troubled line fixed the issue and everything works fine now!
old:
var response = await webRequest.GetResponseAsync();
fixed:
var response = await webRequest.GetResponseAsync().ConfigureAwait(false);
I've used async coding a little bit but I don't really fully understand how to use it -- though I understand the concept and why I need it.
Here's my set up:
I have a Web API that I will call from my ASP.NET MVC app and my Web API will call DocumentDB. In code samples, I see a lot of await keywords while sending queries to DocumentDB.
I'm confused if I need to make my Index action method in my MVC app async?
I'm also confused if my CreateEmployee() method in my Web API should be async?
What is the right way to use async in this scenario?
Here's my code (This code is currently giving me errors because my MVC action method is not async)
---- ASP.NET MVC App Code ----
public ActionResult Index()
{
Employee emp = new Employee();
emp.FirstName = "John";
emp.LastName = "Doe";
emp.Gender = "M";
emp.Ssn = "123-45-6789";
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://myWebApi.com");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.PostAsJsonAsync("hr/create/newemployee", emp);
if (response.IsSuccessStatusCode)
{
emp = await response.Content.ReadAsAsync<Employee>();
}
}
// Display employee info
return View(emp);
}
---- Web API Code ----
private static readonly string endPointUrl = ConfigurationManager.AppSettings["EndPointUrl"];
private static readonly string authorizationKey = ConfigurationManager.AppSettings["AuthorizationKey"];
private static readonly string databaseId = ConfigurationManager.AppSettings["DatabaseId"];
private static DocumentClient client;
public static async Task<Employee> CreateEmployee(Employee emp)
{
try
{
//Create a Document client
using (client = new DocumentClient(new Uri(endPointUrl), authorizationKey))
{
//Get the database
var database = await GetDatabaseAsync();
//Get the Document Collection
var collection = await GetCollectionAsync(database.SelfLink, "Employees");
await client.CreateDocumentAsync(collection.SelfLink, emp);
// Further process employee
}
}
catch
{
// Handle error
}
return employee;
}
private static async Task<DocumentCollection> GetCollectionAsync(string dbLink, string id)
{
DocumentCollection collection = client.CreateDocumentCollectionQuery(dbLink).Where(c => c.Id == id).ToArray().FirstOrDefault();
return collection;
}
private static async Task<Database> GetDatabaseAsync()
{
Database database = client.CreateDatabaseQuery().Where(db => db.Id == databaseId).ToArray().FirstOrDefault();
return database;
}
Here's my explanation
class MainClass
{
public static async Task<String> AsyncMethod(int delay) {
await Task.Delay (TimeSpan.FromSeconds(delay));
return "The method has finished it's execution after waiting for " + delay + " seconds";
}
public static async Task Approach1(int delay)
{
var response = await AsyncMethod (delay); // await just unwraps Task's result
Console.WriteLine (response);
}
public static Task Approach2(int delay)
{
return AsyncMethod(delay).ContinueWith(message => Console.WriteLine(message)); // you could do the same with
}
public static void Main (string[] args)
{
var operation1 = Approach1 (3);
var operation2 = Approach2 (5);
Task.WaitAll (operation1, operation2);
Console.WriteLine("All operations are completed")
}
}
Eventually both Approach1 and Approach2 are identical pieces of code.
The async/await is syntactic sugar around Task API. It takes your async method splits it into parts before await, and after await. The "before" part is executed immediately. The "after" part is getting executed when await operation is completed. You are able to track the second part of operation via the Task API since you get a reference to a Task.
In general async allows to treat a method call as a some sort of long operation that you can reference via the Task API and wait until it is finished and continue with another piece of code. Either via ContinueWith call of via using await in general it's the same.
Before async/await/Task concepts people were using callbacks, but handling errors was as easy as hell, the Task is similar to a concept of callback except that it is able allow handling exceptions more easily.
In general all this Task/async/await mantra is close to concept of promises if it happen that you've worked with jQuery/JavaScript there's a similar concept here's a nice question explaining how it's done there "jQuery deferreds and promises - .then() vs .done()"
Edit: I've just found out that .NET lacks implementation of then functionality similar to one found in jQuery/JavaScript.
The difference between ContinueWith and Then is that Then is able to compose task, and to execute them sequentially while ContinueWith is not, it is able only to launch task in parallel, but it can be easily implemented via the await construct. Here is my updated code containing the whole shebang:
static class Extensions
{
// Implementation to jQuery-like `then` function in .NET
// According to: http://blogs.msdn.com/b/pfxteam/archive/2012/08/15/implementing-then-with-await.aspx
// Further reading: http://blogs.msdn.com/b/pfxteam/archive/2010/11/21/10094564.aspx
public static async Task Then(this Task task, Func<Task> continuation)
{
await task;
await continuation();
}
public static async Task<TNewResult> Then<TNewResult>(
this Task task, Func<Task<TNewResult>> continuation)
{
await task;
return await continuation();
}
public static async Task Then<TResult>(
this Task<TResult> task, Func<TResult,Task> continuation)
{
await continuation(await task);
}
public static async Task<TNewResult> Then<TResult, TNewResult>(
this Task<TResult> task, Func<TResult, Task<TNewResult>> continuation)
{
return await continuation(await task);
}
}
class MainClass
{
public static async Task<String> AsyncMethod1(int delay) {
await Task.Delay (TimeSpan.FromSeconds(delay));
return "The method has finished it's execution after waiting for " + delay + " seconds";
}
public static Task<String> AsyncMethod2(int delay)
{
return Task.Delay (TimeSpan.FromSeconds (delay)).ContinueWith ((x) => "The method has finished it's execution after waiting for " + delay + " seconds");
}
public static async Task<String> Approach1(int delay)
{
var response = await AsyncMethod1 (delay); // await just unwraps Task's result
return "Here is the result of AsyncMethod1 operation: '" + response + "'";
}
public static Task<String> Approach2(int delay)
{
return AsyncMethod2(delay).ContinueWith(message => "Here is the result of AsyncMethod2 operation: '" + message.Result + "'");
}
public static void Main (string[] args)
{
// You have long running operations that doesn't block current thread
var operation1 = Approach1 (3); // So as soon as the code hits "await" the method will exit and you will have a "operation1" assigned with a task that finishes as soon as delay is finished
var operation2 = Approach2 (5); // The same way you initiate the second long-running operation. The method also returns as soon as it hits "await"
// You can create chains of operations:
var operation3 = operation1.ContinueWith(operation1Task=>Console.WriteLine("Operation 3 has received the following input from operation 1: '" + operation1Task.Result + "'"));
var operation4 = operation2.ContinueWith(operation2Task=>Console.WriteLine("Operation 4 has received the following input from operation 2: '" + operation2Task.Result + "'"));
var operation5 = Task.WhenAll (operation3, operation4)
.Then(()=>Task.Delay (TimeSpan.FromSeconds (7)))
.ContinueWith((task)=>Console.WriteLine("After operation3 and 4 have finished, I've waited for additional seven seconds, then retuned this message"));
Task.WaitAll (operation1, operation2); // This call will block current thread;
operation3.Wait (); // This call will block current thread;
operation4.Wait (); // This call will block current thread;
operation5.Wait (); // This call will block current thread;
Console.WriteLine ("All operations are completed");
}
}
you can only use await inside a method if that method is async and async methods need to return Task, Task<T> or void although void returning async methods are reserved for event handlers because the exceptions thrown within them are swallowed and you cannot await their completion or chain subsequent tasks.
I think your Index action needs to be async and return a Task<ActionResult> and your CreateEmployee method needs to be async as well as it is using await inside it.
See Best Practices in Asynchronous Programming for some guidelines on when and how to use async-await
async await
They are tricky to understand.
First of all, in your methods in Web API, you are using async without await. I'm sure you are getting some errors / warning there right?
--
async await are used to return the working thread back to the caller when you are waiting for I/O to be finished. So, yes, you do want to use it both in your MVC and Web API side. Please make sure you understand this sentence before moving on.
--
The thing about async / await is that, if you use it, you have to use it ALL the way through the calling functions, or else it doesn't make sense (and you'll get errors / warning too). This means that whatever library you are using must support it. In this case "DocumentClient". By convention, the methods that support it will end in "Async" and it will return a Task which you can await.
--
So your short answer:
use async await from the very beginning (your controller), and try to make it await whatever long operations it calls. If that is also your code, you should be able to await from there ... and await from there ... until you finally call something that is not your code. If you can await that code that is not yours, then you are set. If you cannot, then you should not use async await form the very beginning.
(no way this made sense)
I have a "rest client" that wraps HttpClient and whose methods are async.
Besides other reasons, I need to control signin/signout process with my rest client so that number of sessions is not exceeded.
The rest client implements IDisposable and upon disposing the client I need to check if the client is "still signed in" and sign out if it is.
Since doing any kind of external calls in Dispose method is considered bad practice, I have something as following
public class MappingsController : RestController
{
[HttpGet]
public async Task<HttpResponseMessage> GetYears()
{
return await ProcessRestCall(async rc => await rc.GetYearsAsync());
}
}
public class RestController : ApiController
{
protected async Task<HttpResponseMessage> ProcessRestCall<T>(Func<RestClient, Task<T>> restClientCallback)
{
RestClient restClient = null;
try
{
var credentials = GetCredentialsFromRequestHeader();
if (credentials == null)
{
return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Missing credentials from header!");
}
var username = credentials["Username"];
var password = credentials["Password"];
restClient = new RestClient(username, password);
var authenticated = await restClient.SignInAsync();
if (!authenticated)
{
return CreateErrorResponseWithRestStatus(HttpStatusCode.Unauthorized, restClient);
}
var result = await restClientCallback(restClient);
// Following works, but since I need to do it in finally block in case exception happens, perhaps It should be done in finally anyways...
//await restClient.SignOutAsync();
var response = Request.CreateResponse(HttpStatusCode.OK, result);
return response;
}
catch (Exception e)
{
return CreateErrorResponseWithRestStatus(HttpStatusCode.BadRequest, restClient, e);
}
finally
{
if (restClient != null)
{
if (restClient.IsSignedIn)
{
//var signedOutOk = restClient.SignOutAsync();//.Result; //<-- problem - this blocks!!!
restClient.SignOutAsync().ConfigureAwait(false); // seems to work, but I am not sure if this is kosher + I can't get return var
//Logger.Warn(CultureInfo.InvariantCulture, m => m("Client was still signed in! Attempt to to sign out was {0}", signedOutOk ? "successful" : "unsuccessful"));
}
restClient.Dispose();
}
}
}
}
The use of .ConfigureAwait(false) is a non-issue. You aren't awaiting on the task at all. Since you don't await it, it doesn't matter what await is configured to do.
What you're doing is just basic fire and forget (which may or may not be acceptable for you).
You should remove the ConfigureAwait(false) no matter what, just because it does nothing and is confusing to the reader. If it's okay for you to send the request to sign out but not actually sign out, then this is okay.
If you need to ensure that restClient.Dispose(); isn't called until the sign out request returns, then you have a bit of a...problem. The problem stems from the fact that the sign out request might be unsuccessful, or much worse, it might not respond at all. You'd need some way of dealing with that.
You can't use await in a finally block, but you can more or less mimic its behavior through continuations. You may need to do something like this:
public static async Task DoStuff()
{
IDisposable disposable = null;
try { }
finally
{
var task = GenerateTask();
var continuation = Task.WhenAny(task, Task.Delay(5000))
.ContinueWith(t =>
{
if (task.IsCompleted) //if false we timed out or it threw an exception
{
var result = task.Result;
//TODO use result
}
disposable.Dispose();
});
}
}
Note that since you aren't using await the task returned from DoStuff will indicate that it is "done" as soon as it hits the finally block for the first time; not when the continuation fires and the object is disposed. That may or may not be acceptable.