Task.ContinueWith execution order - c#

Apparently, I'm not understanding how to use the ContinueWith method. My goal is to execute a task, and when complete, return a message.
Here's my code:
public string UploadFile()
{
if (Request.Content.IsMimeMultipartContent())
{
//Save file
MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));
Task<IEnumerable<HttpContent>> task = Request.Content.ReadAsMultipartAsync(provider);
string filename = "Not set";
task.ContinueWith(o =>
{
//File name
filename = provider.BodyPartFileNames.First().Value;
}, TaskScheduler.FromCurrentSynchronizationContext());
return filename;
}
else
{
return "Invalid.";
}
}
The variable "filename" always returns "Not set". It seems the code within the ContinueWith method is never called. (It does get called if I debug through it line by line in VS.)
This method is being called in my ASP.NET Web API controller / Ajax POST.
What am I doing wrong here?

If you're using an asynchronous operation, the best approach would be to make your operation asynchronous as well, otherwise you'll lose on the advantages of the async call you're making. Try rewriting your method as follows:
public Task<string> UploadFile()
{
if (Request.Content.IsMimeMultipartContent())
{
//Save file
MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));
Task<IEnumerable<HttpContent>> task = Request.Content.ReadAsMultipartAsync(provider);
return task.ContinueWith<string>(contents =>
{
return provider.BodyPartFileNames.First().Value;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
else
{
// For returning non-async stuff, use a TaskCompletionSource to avoid thread switches
TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
tcs.SetResult("Invalid.");
return tcs.Task;
}
}

The reasons for your variable not being set are:
the tasks are instantiated, but not run.
even if the tasks ran, the function would probably return before they finished running so, it would still return "Not set". The fix for this is waiting for the final task (the one setting fileName) to finish.
Your code could be fixed like this:
public string UploadFile()
{
if (Request.Content.IsMimeMultipartContent())
{
//Save file
MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));
Task<IEnumerable<HttpContent>> task = Request.Content.ReadAsMultipartAsync(provider);
string filename = "Not set";
var finalTask = task.ContinueWith(o =>
{
//File name
filename = provider.BodyPartFileNames.First().Value;
}, TaskScheduler.FromCurrentSynchronizationContext());
task.Start();
finalTask.Wait();
return filename;
}
else
{
return "Invalid.";
}
}
The additions are the following:
assigned the return value of task.ContinueWith to a variable called finalTask. We need this task, because we'll wait for it to finish
started the task (the task.Start(); line)
waited for the final task to finish before returning (finalTask.Wait();)
If possible, please consider not implementing this asynchronously, because in the end it's synchronous (you're waiting for it to finish) and the current implementation adds complexity that could probably be avoided.
Consider doing something along these lines (if possible):
public string UploadFile()
{
if (Request.Content.IsMimeMultipartContent())
{
//Save file
MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));
Request.Content.ReadAsMultipart(provider); // don't know if this is really valid.
return provider.BodyPartFileNames.First().Value;
}
else
{
return "Invalid.";
}
}
Disclaimer: I have not actually executed the above code; I just wrote it to illustrate what should be done.

You should return the type Task<T> from the method, in this case it would be a Task<string>.

You are using an asynch operation. If you want to wait for its completion, you have to use the Wait method otherwise of your task:
task.ContinueWith(o =>
{
//File name
filename = provider.BodyPartFileNames.First().Value;
).Wait();
return filename;
Edit:
Some asynch methods start the task as soon as it is created, whereas other ask you to explicitly start them. You have to consult the documentation for each to be sure. In this case, it appears the task does start automatically.

Related

How do you set up some function to run in a background thread but keep the normal return type instead of the Task<int>?

I have a function from the one service to that will get the count of all files inside a directory. And another service will get that int number to do some stuff with it.
public int GetNumberOfAvatarsInFile()
{
try
{
var path = GetAvatarsFilePath();
var numberOfAvatars = Directory.GetFiles(path, "*.*", SearchOption.TopDirectoryOnly).Length;
return numberOfAvatars;
}
catch (Exception exception)
{
var message = $"Error While Getting Total Numbers Of Avatars at {DateTime.Now}\n\t" +
$"Error: {JsonConvert.SerializeObject(exception)}";
sentryClient.CaptureMessage(message, SentryLevel.Error);
return 1;
}
}
private string GetAvatarsFilePath()
{
var webRootPath = webHostEnvironment.WebRootPath;
var path = Path.Combine(webRootPath, "path");
return path;
}
The other service will use this function like this
private int GetMaximumAvatarId() => avatarService.GetNumberOfAvatarsInFile();
How do I set up so that all these file getting logic and string combine will be separated to a background thread/another thread by either Task.Run or something similar?
When I try to set up the GetNumberOfAvatarsInFile() by implementing await Task.Run(async () => LOGIC+Return int);
I have to return a Task rather than the int from the other service that is calling it as well, which is not desirable since those are other people code and I should not change them. Also as far as I know all the Path.Combine and Directory functions do not employ any awaiter.
Is there a way to implement this?
As mentioned in the comments, the best practice is to provide async methods to the caller and use async all the way (see this article). However there are 2 things that can already be done:
1. Make your I/O method run asynchronously in a separate thread.
2. Have callers call your method asynchronously even if the implementation is synchronous.
The implementations on client side and on service side are independent. Here is a commented example that I hope shows how to do this. Most of the code below is unnecessary and is there only to illustrate what happens when multiple callers call your method and what is executed when. You may change the Thread.Sleep() values to simulate different execution time.
I also added a side note regarding the value you return in the Exception, that does not look ok to me.
public class Program
{
public static void Main()
{
// These simulate 3 callers calling your service at different times.
var t1 = Task.Run(() => GetMaximumAvatarId(1));
Thread.Sleep(100);
var t2 = Task.Run(() => GetMaximumAvatarId(2));
Thread.Sleep(2000);
var t3 = Task.Run(() => GetMaximumAvatarId(3));
// Example purposes.
Task.WaitAll(t1, t2, t3);
Console.WriteLine("MAIN: Done.");
Console.ReadKey();
}
// This is a synchronous call on the client side. This could very well be implemented
// as an asynchronous call, even if the service method is synchronous, by using a
// Task and having the caller await for it (GetMaximumAvatarIdAsync).
public static int GetMaximumAvatarId(int callerId)
{
Console.WriteLine($"CALLER {callerId}: Calling...");
var i = GetNumberOfAvatarsInFile(callerId);
Console.WriteLine($"CALLER {callerId}: Done -> there are {i} files.");
return i;
}
// This method has the same signature as yours. It's synchronous in the sense that it
// does not return an awaitable. However it now uses `Task.Run` in order to execute
// `Directory.GetFiles` in a threadpool thread, which allows to run other code in
// parallel (in this example `Sleep` calls, in real life useful code). It finally
// blocks waiting for the result of the task, then returns it to the caller as an int.
// The "callerId" is for the example only, you may remove this everywhere.
public static int GetNumberOfAvatarsInFile(int callerId)
{
Console.WriteLine($" SERVICE: Called by {callerId}...");
var path = GetAvatarsFilePath();
var t = Task.Run(() => Directory.GetFiles(path, "*.*", SearchOption.TopDirectoryOnly).Length);
// Simulate long work for a caller, showing the caller.
Console.WriteLine($" SERVICE: Working for {callerId}...");
Thread.Sleep(500);
Console.WriteLine($" SERVICE: Working for {callerId}...");
Thread.Sleep(500);
Console.WriteLine($" SERVICE: Working for {callerId}...");
Thread.Sleep(500);
Console.WriteLine($" SERVICE: Blocking for {callerId} until task completes.");
return t.Result; // Returns an int.
// --------------------------------------------------------
// Side note: you should return `-1` in the `Exception`.
// Otherwise it is impossible for the caller to know if there was an error or
// if there is 1 avatar in the file.
// --------------------------------------------------------
}
// Unchanged.
private string GetAvatarsFilePath()
{
var webRootPath = webHostEnvironment.WebRootPath;
var path = Path.Combine(webRootPath, "path");
return path;
}
}

How can I await until I receive a callback/notification without busy-waiting?

I understand how I can await on library code to wait for a network request or other long-running action to complete, but how can I await on my own long-running action without busy waiting?
This is the busy-waiting solution. How can I make it event-driven?
public async Task<DataBlock> ClickSend() {
// await until someone calls DataReceived()
while (_Block == null) {
await Task.Yield();
}
return _Block;
}
// Something external calls this function.
// Once called, ClickSend should complete with this data.
public void DataReceived(DataBlock data_block) {
_Block = data_block;
}
DataBlock _Block = null;
(This question is completely different from How do I await events in C#? which is asking how to await on event handlers, but very similar to Promise equivalent in C#.)
Generally in concurrency a "future" is placeholder for a return value and it has an associated "promise" that is fulfilled to pass the final return value.
In C#, they have different names: the future is a Task and the promise is a TaskCompletionSource.
You can create a promise, await on it, and then fulfill it when you get your callback:
TaskCompletionSource<DataBlock> _Promise = new TaskCompletionSource<DataBlock>();
public async Task<DataBlock> ClickSend() {
DataBlock block = await _Promise.Task;
return block;
}
public void DataReceived(DataBlock data_block) {
_Promise.TrySetResult(data_block);
}
Here's an executable example.

Calling AWS RDS CreateDBSnapshotAsync Asynchronously "Set It And Forget It"

In an AWS Lambda function, I would like to be able to call a component to create a RDS DB Snapshot. There is an async method on the client named CreateDBSnapshotAsync. But, because this is AWS Lambda, I only have 5 minutes to complete the task. So, if I await it, the AWS Lambda function will timeout. And, apparently when it times out, the call is cancelled and then the snapshot is not completed.
Is there some way I can make the call in a COMPLETELY asynchronously way so that once I invoke it, it will complete no matter if my Lambda function times out or not?
In other words, I don't care about the result, I just want to invoke the process and move on, a "set it and forget it" mentality.
My call (without the await, obviously) is as below
using (var rdsClient = new AmazonRDSClient())
{
Task<CreateDBSnapshotResponse> response = rdsClient.CreateDBSnapshotAsync(new CreateDBSnapshotRequest($"MySnapShot", instanceId));
}
As requested, here's the full method:
public async Task<CloudFormationResponse> MigrateDatabase(CloudFormationRequest request, ILambdaContext context)
{
LambdaLogger.Log($"{nameof(MigrateDatabase)} invoked: " + JsonConvert.SerializeObject(request));
if (request.RequestType != "Delete")
{
try
{
var migrations = this.Context.Database.GetPendingMigrations().OrderBy(b=>b).ToList();
for (int i = 0; i < migrations.Count(); i++)
{
string thisMigration = migrations [i];
this.ApplyMigrationInternal(thisMigration);
}
this.TakeSnapshotAsync(context,migrations.Last());
return await CloudFormationResponse.CompleteCloudFormationResponse(null, request, context);
}
catch (Exception e)
{
LambdaLogger.Log(e.ToString());
if (e.InnerException != null) LambdaLogger.Log(e.InnerException.ToString());
return await CloudFormationResponse.CompleteCloudFormationResponse(e, request, context);
}
}
return await CloudFormationResponse.CompleteCloudFormationResponse(null, request, context);
}
internal void TakeSnapshotAsync(ILambdaContext context, string migration)
{
var instanceId = this.GetEnvironmentVariable(nameof(DBInstance));
using (var rdsClient = new AmazonRDSClient())
{
Task<CreateDBSnapshotResponse> response = rdsClient.CreateDBSnapshotAsync(new CreateDBSnapshotRequest($"{instanceId}{migration.Replace('_','-')}", instanceId));
while (context.RemainingTime > TimeSpan.FromSeconds(15))
{
Thread.Sleep(15000);
}
}
}
First refactor that sub function to use proper async syntax along with the use of Task.WhenAny.
internal async Task TakeSnapshotAsync(ILambdaContext context, string migration) {
var instanceId = this.GetEnvironmentVariable(nameof(DBInstance));
//don't wrap in using block or it will be disposed before you are done with it.
var rdsClient = new AmazonRDSClient();
var request = new CreateDBSnapshotRequest($"{instanceId}{migration.Replace('_','-')}", instanceId);
//don't await this long running task
Task<CreateDBSnapshotResponse> response = rdsClient.CreateDBSnapshotAsync(request);
Task delay = Task.Run(async () => {
while (context.RemainingTime > TimeSpan.FromSeconds(15)) {
await Task.Delay(15000); //Don't mix Thread.Sleep. use Task.Delay and await it.
}
}
// The call returns as soon as the first operation completes,
// even if the others are still running.
await Task.WhenAny(response, delay);
}
So if the RemainingTime runs out, it will break out of the call even if the snap shot task is still running so that the request does not time out.
Now you should be able to await the snapshot while there is still time available in the context
public async Task<CloudFormationResponse> MigrateDatabase(CloudFormationRequest request, ILambdaContext context) {
LambdaLogger.Log($"{nameof(MigrateDatabase)} invoked: " + JsonConvert.SerializeObject(request));
if (request.RequestType != "Delete") {
try {
var migrations = this.Context.Database.GetPendingMigrations().OrderBy(b=>b).ToList();
for (int i = 0; i < migrations.Count(); i++) {
string thisMigration = migrations [i];
this.ApplyMigrationInternal(thisMigration);
}
await this.TakeSnapshotAsync(context, migrations.Last());
return await CloudFormationResponse.CompleteCloudFormationResponse(null, request, context);
} catch (Exception e) {
LambdaLogger.Log(e.ToString());
if (e.InnerException != null) LambdaLogger.Log(e.InnerException.ToString());
return await CloudFormationResponse.CompleteCloudFormationResponse(e, request, context);
}
}
return await CloudFormationResponse.CompleteCloudFormationResponse(null, request, context);
}
This should also allow for any exceptions thrown by the RDS client to be caught by the currently executing thread. Which should help with troubleshooting any exception messages.
Some interesting information from documentation.
Using Async in C# Functions with AWS Lambda
If you know your Lambda function will require a long-running process, such as uploading large files to Amazon S3 or reading a large stream of records from DynamoDB, you can take advantage of the async/await pattern. When you use this signature, Lambda executes the function synchronously and waits for the function to return a response or for execution to time out.
From docs about timeouts
Function Settings
...
Timeout – The amount of time that Lambda allows a function to run before stopping it. The default is 3 seconds. The maximum allowed value is 900 seconds.
If getting a HTTP timeout then shorten the delay but leave the long running task. You still use the Task.WhenAny to give the long running task an opportunity to finish first even if that is not the expectation.
internal async Task TakeSnapshotAsync(ILambdaContext context, string migration) {
var instanceId = this.GetEnvironmentVariable(nameof(DBInstance));
//don't wrap in using block or it will be disposed before you are done with it.
var rdsClient = new AmazonRDSClient();
var request = new CreateDBSnapshotRequest($"{instanceId}{migration.Replace('_','-')}", instanceId);
//don't await this long running task
Task<CreateDBSnapshotResponse> response = rdsClient.CreateDBSnapshotAsync(request);
Task delay = Task.Delay(TimeSpan.FromSeconds(2.5));
// The call returns as soon as the first operation completes,
// even if the others are still running.
await Task.WhenAny(response, delay);
}

Async Function Not Getting Called

Please forgive me for any noobish mistakes seen below, I'm learning some of the concepts I'm attempting to work with.
Problem:
While debugging my app, I was able to call an async function with Task.Start(). I felt that the app was in a working state for the phase I'm in so removed all breakpoints with CTRL + SHIFT + F9.
Once I ran the app with no breakpoints it would fail due to a property not getting populated. Now when I try to debug any breakpoint I set in the async function that handles most of the work is longer hit. It's like it is getting skipped. Can anyone see a reason why GetWowAuctionFileInfo isn't being called?
GetWowAuctionFileInfo is what is not getting called, or at least appears to be not getting called.
Thanks.
Relevant Code
Caller Function
private void buttonTestJSFCHI_Click(object sender, RoutedEventArgs e)
{
JSON_Worker w = new JSON_Worker();
w.StartTask("FileInfo", "https://us.api.battle.net/wow/auction/data/medivh?locale=en_US&apikey=<guid>");
foreach (string res in w.ReturnedData)
{
textBoxResults.Text += res;
}
}
Called Functions
public void StartTask(string TaskName, string optionalUri= "no_uri_passed")
{
if (TaskName == "FileInfo")
{
//Need to use a lamba expression to call a delegate with a parameter
if (!(optionalUri == "no_uri_passed"))
{
Task t = new Task(() => GetWowAuctionFileInfo(optionalUri));
t.Start();
//Func<string> function = new Func<string>(() => GetWowAuctionFileInfo(optionalUri));
//Task<string> tInfo = Task<string>.Factory.StartNew(() => GetWowAuctionFileInfo(optionalUri));
}
}
}
private async void GetWowAuctionFileInfo(string auctionInfoUri)
{
RealmJSFileCheck realmInfoObject;
List<string> returnValue = new List<string>();
try
{
using (HttpClient client = new HttpClient())
{
for (int attempt = 0; attempt < 3; attempt++)
{
var response = await client.GetAsync(auctionInfoUri);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
realmInfoObject = JsonConvert.DeserializeObject<RealmJSFileCheck>(content);
returnValue = ConvertFileInfoToConsumableList(realmInfoObject);
//returnValue = realmInfoObject.files.ToString();
break;
}
}
}
}
catch (InvalidOperationException iOpEx)
{
//recieved this when an invalid uri was passed in
}
ReturnedData = returnValue;
}
private List<string> ConvertFileInfoToConsumableList(RealmJSFileCheck jsfc)
{
List<string> returnData = new List<string>();
if (jsfc.files.Count > 0)
{
StringBuilder sb = new StringBuilder();
sb.Append("File URL: ");
sb.Append(jsfc.files[0].url);
returnData.Add(sb.ToString());
sb = new StringBuilder();
sb.AppendLine("Last Modified: ");
sb.Append(jsfc.files[0].lastModified);
returnData.Add(sb.ToString());
}
else
{
returnData.Add("No File Info Found");
}
return returnData;
}
UPDATE
Thanks again all for the detailed commentary. I've gone through much documentation regarding Task usage and learned a lot in this exercise. I'm marking the answer from #Johnathon as the solution because it provided exactly what I was asking for and provided a very helpful link for more information.
Your GetWowAuctionFileInfo method is an asynchronous method, and you await an async call within it without returning a Task. In general it is bad practice to use async void. Instead, turn your GetWowAuctionFileInfo method into async Task<List<string>> GetWowAuctionFileInfo. This will let you await the GetAsync call, parse the data, and return the collection to the caller without having to use a ReturnObject.
private async Task<List<string>> GetWowAuctionFileInfo(string auctionInfoUri)
{
RealmJSFileCheck realmInfoObject;
List<string> returnValue = new List<string>();
try
{
using (HttpClient client = new HttpClient())
{
for (int attempt = 0; attempt < 3; attempt++)
{
var response = await client.GetAsync(auctionInfoUri);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
realmInfoObject = JsonConvert.DeserializeObject<RealmJSFileCheck>(content);
// You can just return the List<T> now.
return ConvertFileInfoToConsumableList(realmInfoObject);
//returnValue = realmInfoObject.files.ToString();
break;
}
}
}
}
catch (InvalidOperationException iOpEx)
{
//recieved this when an invalid uri was passed in
}
}
Because the method was originally async void, you could not await the calling of it in your buttonTestJSFCHI_Click. Now that we've made it all Task based, you can await it within your event handler. Note that event handlers are generally the only acceptable place to use async void. Any time you are responsible for the creation of the methods, and not constrained by a contract (like event handlers), you should always return a Task on your async methods.
private async void buttonTestJSFCHI_Click(object sender, RoutedEventArgs e)
{
JSON_Worker w = new JSON_Worker();
List<string> results = await w.StartTask("FileInfo", "https://us.api.battle.net/wow/auction/data/medivh?locale=en_US&apikey=<guid>");
foreach (string res in results)
{
textBoxResults.Text += res;
}
}
public async Task<List<string>> StartTask(string TaskName, string optionalUri= "no_uri_passed")
{
if (TaskName == "FileInfo")
{
//Need to use a lamba expression to call a delegate with a parameter
if (!(optionalUri == "no_uri_passed"))
{
// Since the GetWowAuctionFileInfo now returns Task, we don't need to create a new one. Just await the Task given back to us, and return the given result.
return await GetWowAuctionFileInfo(optionalUri);
}
}
}
The reason you saw the expected result while debugging is because the debug session was slow enough that the async operation completed in time for your code to use it. When running the app outside of the debugger, it runs faster than the async operation could complete, preventing you from seeing the data. Thus the need to await the entire async call stack, so you can prevent further execution from happening down that code-path until you receive all of the desired data.
Microsoft has a good write up on Task based programming, I'd take a read through it to help you understand it some.
EDIT
Just to clarify, when you return a Task<T> on your methods, you will be given the result when you await. For example:
List<string> result = await StartTask();
Even though StartTask returns Task<List<string>>, the await operation will wait for the StartTask() method to complete, and then unwrap the result from the Task<T> object and give you the result back automatically. So don't let the method signature fool you, if you await it, you will be given back the resulting data, and not the actual Task itself. There won't be any need for you to pull the data out of the Task manually.
Because you not waiting for result.
You loop with ReturnedData before it was assigned with data.
I think you don't need to create new Task at all. Make GetWowAuctionFileInfo method properly asynchronous which returns Task.
private async Task GetWowAuctionFileInfo(string auctionInfoUri)
{
// same code
}
Change StartTask to return Task. Because we not awaiting result here we don't need make method asynchronous.
Suggest to change name of this method to LoadData for example, which give more information about what this method does.
public Task StartTask(string TaskName, string optionalUri= "no_uri_passed")
{
if (TaskName == "FileInfo")
{
//Need to use a lamba expression to call a delegate with a parameter
if (!(optionalUri == "no_uri_passed"))
{
return GetWowAuctionFileInfo(optionalUri) // this will return Task
}
}
// if validation fails - return completed task or throw exception
return Task.CompletedTask;
}
Then you can call it in Button_Click event handler
private async void buttonTestJSFCHI_Click(object sender, RoutedEventArgs e)
{
JSON_Worker w = new JSON_Worker();
await w.StartTask("FileInfo", "yourUrl");
// This line will be executed only after asynchronous methods completes succesfully
// or exception will be thrown
foreach (string res in w.ReturnedData)
{
textBoxResults.Text += res;
}
}

How do I set up the continuations for HttpClient correctly?

I'm using .NET 4.0, so I can't use the async/await keywords.
After I laboriously set up tasks and continuations instead of just calling .Result, all I got for my efforts was a mess and it runs 46% slower on a workload of a few dozen HTTP GETs. (I get similar perf degradation if I call the workload in serial or in a Parallel loop)
What must I do to see any performance benefit?
//Slower code
public UserProfileViewModel GetAsync(Guid id)
{
UserProfileViewModel obj = null;//Closure
Task result = client.GetAsync(id.ToString()).ContinueWith(responseMessage =>
{
Task<string> stringTask = responseMessage.Result
.Content.ReadAsStringAsync();
Task continuation = stringTask.ContinueWith(responseBody =>
{
obj = JsonConvert
.DeserializeObject<UserProfileViewModel>(responseBody.Result);
});
//This is a child task, must wait before returning to parent.
continuation.Wait();
});
result.Wait();
return obj;
}
//Faster code
public UserProfileViewModel GetSynchr(Guid id)
{
//Asych? What's is that?
HttpResponseMessage response = client.GetAsync(id.ToString()).Result;
string responseBody = response.Content.ReadAsStringAsync().Result;
return JsonConvert.DeserializeObject<UserProfileViewModel>(responseBody);
}
You are using "async" methods but doing everything synchronously. That certainly won't be any better than doing everything synchronously with the synchronous methods.
Take a look at this:
public Task<UserProfileViewModel> GetAsync(Guid id)
{
var uri = id.ToString();
return client.GetAsync(uri).ContinueWith(responseTask =>
{
var response = responseTask.Result;
return response.Content.ReadAsStringAsync().ContinueWith(jsonTask =>
{
var json = jsonTask.Result;
return JsonConvert.DeserializeObject<UserProfileViewModel>(json);
});
}).Unwrap();
}
Notice how the method returns a Task and the continuations are returned from the method. This allows your method to return almost instantly, giving the caller a handle to the running work and whatever continuations need to happen. The returned task will only be complete once everything is done, and it's result will be your UserProfileViewModel.
The Unwrap method takes a Task<Task<UserProfileViewModel>> and turns it into a Task<UserProfileViewModel>.

Categories