Await Task outside of using statement - c#

I have code:
Task<string> pageIdTask;
using (var seedSession = this.dbSessionManager.Start(agencyId))
{
pageIdTask = seedSession.Query<ModelInfo>()
.Where(mi=> mi.Id == modelId)
.Select(mi=> mi.Tag)
.SingleAsync();
}
var tokenTask = this.tokenManager.GetAsync(agencyId);
await Task.WhenAll(pageIdTask, tokenTask);
string pageId = pageIdTask.Result;
var token = tokenTask.Result;
Is it correct? I mean is it ok to await Task if it was initialized in using statement?
Or better to await Task in using statement?

There's no technical limitation in awating a task created in the scope of a using() statement outside of it, but in most cases that means the task's logic depends on whatever was inside the using() which is now disposed.
So in general something like this is ok:
Task task;
using(var something = GetSomething()){
task = otherThing.CompletelyUnrelatedLogic();
}
task.Wait(); // no prob
But in most actual cases will be like:
Task task;
using(var something = GetSomething()){
task = something.TaskThatWillFailAndExplodeIfExecutedWhenSomethingIsDisplosed();
}
task.Wait(); // BOOM

Related

Point of Task.Run if it will be awaited immediately

I have this method:
public async Task DoSomething()
{
string token = string.Empty;
await Task.Run(() =>
{
using (var sc = _dbManager.CreateSession())
{
// somework...
}
});
using (var resp = await client.PostAsync(authenticationEndpoint,
new FormUrlEncodedContent(authenticateRequest), stoppingToken))
{
resp.EnsureSuccessStatusCode();
string stringResponse = await resp.Content.ReadAsStringAsync(stoppingToken);
var response = JsonConvert.DeserializeObject<Response>(stringResponse);
token = response.AccessToken;
}
}
Now I do not understand why I need to use await Task.Run? It's confusing for me to understand logic behind this, as I'm awaiting the task to finish anyway, so why should I put it in the Task in the first place?
My lead told me that this was the way to go, and also explained to me that if I don't put that in Task, the code will be executed again from the top, after await client.PostAsync.

Task null using Task.Run and Parallel.For

I have two services that ultimately both update the same object, so we have a test to ensure that the writes to that object complete (Under the hood we have retry policies on each).
9 times out of 10, one or more of the theories will fail, with the task.ShouldNotBeNull(); always being the assertion to fail. What am i getting wrong with the async code in this sample? Why would the task be null?
[Theory]
[InlineData(1)]
[InlineData(5)]
[InlineData(10)]
[InlineData(20)]
public async Task ConcurrencyIssueTest(int iterations)
{
var orderResult = await _driver.PlaceOrder();
var tasksA = new List<Task<ApiResponse<string>>>();
var tasksB = new List<Task<ApiResponse<string>>>();
await Task.Run(() => Parallel.For(1, iterations,
x =>
{
tasksA.Add(_Api.TaskA(orderResult.OrderId));
tasksB.Add(_Api.TaskB(orderResult.OrderId));
}));
//Check all tasks return successful
foreach (var task in tasksA)
{
task.ShouldNotBeNull();
var result = task.GetAwaiter().GetResult();
result.ShouldNotBeNull();
result.StatusCode.ShouldBe(HttpStatusCode.OK);
}
foreach (var task in tasksB)
{
task.ShouldNotBeNull();
var result = task.GetAwaiter().GetResult();
result.ShouldNotBeNull();
result.StatusCode.ShouldBe(HttpStatusCode.OK);
}
}
}
There's no need for Tasks and Parrallel looping here. I'm presuming that your _api calls are IO bound? You want something more like this:
var tasksA = new List<Task<ApiResponse<string>>>();
var tasksB = new List<Task<ApiResponse<string>>>();
//fire off all the async tasks
foreach(var it in iterations){
tasksA.Add(_Api.TaskA(orderResult.OrderId));
tasksB.Add(_Api.TaskB(orderResult.OrderId));
}
//await the results
await Task.WhenAll(tasksA).ConfigureAwait(false);
foreach (var task in tasksA)
{
//no need to get GetAwaiter(), you've awaited above.
task.Result;
}
//to get the most out of the async only await them just before you need them
await Task.WhenAll(tasksB).ConfigureAwait(false);
foreach (var task2 in tasksB)
{
task2.Result;
}
this will fire all your api calls async then block while the results return. You Parallel for and tasks are just using additional thread pool threads to zero benefit.
If _api is CPU bound you could get benefit from Task.Run but I'm guessing these are web api or something. So the Task.Run is doing nothing but using an additional thread.
As others have suggested, remove the Parallel, and await on all tasks to finish before asserting them.
I would also recommend to remove .Result from each task, and await them instead.
public async Task ConcurrencyIssueTest(int iterations)
{
var orderResult = await _driver.PlaceOrder();
var taskA = _Api.TaskA(orderResult.OrderId);
var taskB = _Api.TaskB(orderResult.OrderId);
await Task.WhenAll(taskA, taskB);
var taskAResult = await taskA;
taskAResult.ShouldNotBeNull();
taskAResult.StatusCode.ShouldBe(HttpStatusCode.OK);
var taskBResult = await taskB;
taskBResult.ShouldNotBeNull();
taskBResult.StatusCode.ShouldBe(HttpStatusCode.OK);
}

Waiting for Tasks to finish in a list with ContinueWith

Update
Added the missing code for adding in taskList
I have a list of task, that I await on..
var files = Directory.GetFiles(myFilesDirectory);
var listOfTasks = new List<Tasks>();
files.ToList().ForEach(file => {
var localFile = file // to avoid any closure issue
listOfTasks.Add(ProcessMyFileTask(localFile));
});
await Task.WhenAll(listOfTasks.ToArray());
Console.WriteLine("All done!");
Here's the ProcessMyFileTask
private async Task<List<string>> ProcessMyFileTask(string filePath)
{
using (var streamReader = File.OpenText(filePath))
{
string line;
if ((line = await streamReader.ReadLineAsync()) != null)
{
return DumpHexInLog(line);
}
return null;
}
}
The message shows up when all files are processed. But if I add a continuation task, like this..
var files = Directory.GetFiles(myFilesDirectory);
var listOfTasks = new List<Tasks>();
files.ToList().ForEach(file => {
var localFile = file // to avoid any closure issue
listOfTasks.Add(ProcessMyFileTask(localFile).ContinueWith(list =>
ValidateHexDumpsTask(list.Result, localFile)));
});
await Task.WhenAll(listOfTasks.ToArray());
Console.WriteLine("All done!");
Then what Tasks will be awaited on? I mean would "All Done!" come after all the ProcessMyFileTask is done? Or will it come after all the ValidateHexDumpsTask are done too?
When I tested it, it came after the ValidateHexDumpsTask but I am not sure if that will certainly be always the case, as this might have been because of some threading condition or such.
It will complete only when both the ProcessMyFiles methods and ValidateHexDumps are done.
However, ContinueWith is not recommended. It's a low-level, dangerous API. You should use await instead:
var files = Directory.GetFiles(myFilesDirectory);
var listOfTasks = files.Select(ProcessAndValidateAsync);
await Task.WhenAll(listOfTasks);
Console.WriteLine("All done!");
async Task ProcessAndValidateAsync(string file)
{
var list = await ProcessMyFileTask(localFile);
ValidateHexDumps(list, localFile);
}
WhenAll will be completed when ValidateHexDumps has finished running on each of the items.
ContinueWith returns a Task that represents the completion of the continuation, not the completion of the task that it is a continuation of.

Cancelling a Long Running Task Delay

I am trying to cancel a async delay task (Task.Delay) that was created by a web api request, with an another web api request that issues cancel. It does not look like it cancelled the timer for task.delay. Here is the code snippet that I am trying to implement. For your information I am using Application object to store the CancellationTokenSource object to retrieve the token source across multiple requests.
Update Question is I am expecting the task to be cancelled by throwing an exception from the code. But it never happened. How do I make this code to cancel the task.delay?
using Microsoft.Practices.Unity;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
namespace WebApplication8.Controllers
{
public class TaskController : ApiController
{
[HttpGet]
public async Task<string> CreateTask(int id)
{
var tokenSource = new CancellationTokenSource();
var concurrentTokens = GetConcurrentTokens();
concurrentTokens.TryAdd(id, tokenSource);
CancellationToken token = tokenSource.Token;
token.ThrowIfCancellationRequested();
await Task.Delay(50000,token);
return "Task Created";
}
[HttpGet]
public async Task<string> Cancel(int id)
{
var concurrentTokens = GetConcurrentTokens();
CancellationTokenSource item = concurrentTokens.First(t => t.Key == id).Value;
item.Cancel();
item.Dispose();
var tokenSource2 = new CancellationTokenSource();
concurrentTokens.TryRemove(id,out tokenSource2);
return "Cancelled";
}
private ConcurrentDictionary<long, CancellationTokenSource> GetConcurrentTokens()
{
var tokens = HttpContext.Current.Application["Tokens"];
if (tokens == null)
{
tokens = new ConcurrentDictionary<long, CancellationTokenSource>();
HttpContext.Current.Application["Tokens"] = tokens;
}
return (ConcurrentDictionary<long, CancellationTokenSource>) tokens;
}
}
}
I think it get cancelled , you can try it by adding try catch like this:
try
{
await Task.Delay(5000, token);
}
catch(TaskCanceledException ex)
{
}
And you will see it enters the catch block, the method doesn't return any thing because TaskCanceledException
Your code looks correct, I tested it like this:
var tc = new TaskController();
var backTask1 = tc.CreateTask(1);
var backTask2 = tc.CreateTask(2);
// task 2 gets cancelled
await tc.Cancel(2);
try
{
var res2 = await backTask2;
}
catch (OperationCanceledException) { }
// task 1 waits
await backTask1;
n.b. that:
token.ThrowIfCancellationRequested() does nothing just after you created the CancellationTokenSource - this method literally just throws an exception if some code has cancelled the source already.
If a task is started without being awaited, you won't see exceptions it raised until it is awaited.
Try using TaskCompletionSource. It works for me, but I'm not sure if this is what you are looking for.
var source = new CancellationTokenSource();
var concurrentTokens = GetConcurrentTokens();
concurrentTokens.TryAdd(id, source);
var completionSource = new TaskCompletionSource<object>();
source.Token.Register(() => completionSource.TrySetCanceled());
var task = Task.Delay(50000, source.Token);
// Continue when any of these are done.
await Task.WhenAny(task, completionSource.Task);
if (task.IsCanceled)
{
return "Task was not created";
}
return "Task Created";
Also, there's no need to keep Cancel async. You can return a string instead of Task<string>.

ContinueWith not being called async

I'm trying to httpget some values before I execute the next line in the statement. I need to wait for this call to return so I can use the values I deserialize into a list.
Since I want the async call to finish first, I wrapped this in a Task. It worked and it's successfully retrieving the JSON. I then can't get it to go into the ContinueWith block. Why is it not going in there, even when the task is completed(?).
How I'm calling it:
Task f = Task.Run(() =>
{
var task = RetrieveDataAsync();
}).ContinueWith((antecedent) =>
{
pokemonListActivityListView.Adapter = new PokemonListAdapter(this, pokemonList);
pokemonListActivityListView.FastScrollEnabled = true;
pokemonListActivityListView.ItemClick += PokemonListActivityListViewOnItemClick;
});
RetrieveDataAsync method:
private async Task RetrieveDataAsync()
{
string dataUri = "http://pokemonapp6359.azurewebsites.net/Pkmn/GetAllPokemon";
using (var httpClient = new HttpClient())
{
var uri = new Uri(string.Format(dataUri, string.Empty));
//DisplayProgressBar(BeforeOrAfterLoadState.Before, progressBarView);
var response = await httpClient.GetAsync(uri);
//DisplayProgressBar(BeforeOrAfterLoadState.After, progressBarView);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
pokemonList = JsonConvert.DeserializeObject<List<PokemonDTO>>(content);
//canPressButtons = true; //fix this when implement local db
Utilities.Utilities.ShowToast(this, "Successfully fetched data", ToastLength.Short, GravityFlags.Center);
return;
}
else
{
Utilities.Utilities.ShowToast(this, "Failed to fetch data", ToastLength.Short, GravityFlags.Center);
return;
}
}
}
Why is my code not going into the ContinueWith when I've got the JSON ? Thanks!
Instead of just assigning the hot task, you are not waiting on it to finish. You have to call ContinueWith on that task:
var task = RetrieveDataAsync();
task.ContinueWith( ... );
Or await the task:
var result = await RetrieveDataAsync();
... // continue
The problem is that you're ignoring the task returned from RetrieveDataAsync. If you return that task from your lambda expression, then it will behave as you expect.
On a side note, you shouldn't use ContinueWith; it's a dangerous API. Use await instead of ContinueWith:
await Task.Run(() => RetrieveDataAsync());
pokemonListActivityListView.Adapter = new PokemonListAdapter(this, pokemonList);
pokemonListActivityListView.FastScrollEnabled = true;
pokemonListActivityListView.ItemClick += PokemonListActivityListViewOnItemClick;

Categories