My code does not have a problem actually but its a case of performance issue which I may run into.
According to what I know about tasks, a task which starts first say TaskA can be running parallel with another task say TaskB but TaskB may finish and exit its thread before TaskA completes its work. So in my code, I'm trying to proceed with an operation but first knwow the task status. Is it true that this task called TaskResult in my code will complete before the preceeding lines of codes will run or should I use a Task.Wait() on it if that's what I want?
public virtual async Task<OperationResult> DeleteAsync(Expression<Func<TEntity, bool>> expression)
{
Task TaskResult = await _DbContext.Set<TEntity>().RemoveRange(IEnumerable<TEntity>);
if (TaskResult.Status == TaskStatus.RanToCompletion)
{
return new OperationResult()
{
Message = "",
ReturnObject = null,
Status = OperationStatus.Deleted,
Succeeded = true
};
}
if (TaskResult.Status == TaskStatus.Faulted)
{
return new OperationResult()
{
Message = "",
ReturnObject = null,
Status = OperationStatus.UnknownError,
Succeeded = false
};
}
}
The await keyword will stop execution of your method until the Task is completed. Once complete, await will either return the result of the Task, or throw an exception that happened inside the Task (if there was one). So there is no point testing to see if a Task faulted if you already awaited it, because if it faulted, await would have thrown an exception.
Just wrap it in a try/catch block.
But your code won't even compile because:
DbSet<T>.RemoveRange doesn't return a Task because it doesn't actually talk to the database at all. It just removes the records in memory. You need to call DbContext.SaveChangesAsync() for your changes to be committed.
You're not actually passing a list of objects to RemoveRange. (Did you change that code for this example?)
Once you await a Task, the await keyword returns the result of that Task, not the Task itself.
Here's an example of how you could write that:
public virtual async Task<OperationResult> DeleteAsync(Expression<Func<TEntity, bool>> expression) {
_DbContext.Set<TEntity>().RemoveRange(somethinghere);
try {
//commit the changes
var result = await _DbContext.SaveChangesAsync();
//result will be an int (the number of records changed), not a Task
//if we got this far, we succeeded
return new OperationResult()
{
Message = "",
ReturnObject = null,
Status = OperationStatus.Deleted,
Succeeded = true
};
} catch (Exception e) {
return new OperationResult()
{
Message = "", //you can use e.Message here if you want
ReturnObject = null,
Status = OperationStatus.UnknownError,
Succeeded = false
};
}
}
Note that you could just put:
await _DbContext.SaveChangesAsync();
If you don't intend on using result.
Related
I'm kinda new at async programming so don't uderstand many things. Please, I need a little bit more help. I did as was recommened in my other question.
public async Task<TResponse> SendRequestAsync<TResponse>(Func<Task<TResponse>> sendAsync)
{
int timeout = 15;
if (await Task.WhenAny(sendAsync, Task.Delay(timeout) == sendAsync))
{
return await sendAsync();
}
else
{
throw new Exception("time out!!!");
}
}
But I need to get a result of sendAsync() and return it. So have I questions:
1) What the best way to do that and how to use Task.Delay with Func<Task<TResponse>>(or may be something instead of it)? I can't figure out how convert(or something) Func to Task.
2) It seems that return await sendAsync() inside if permorms request once more. It is not great. Can I get result of my Func<Task<..>> inside if somehow?
Since you are new in async programming - it's better to not put too much stuff in one statement and better split that:
public async Task<TResponse> SendRequestAsync<TResponse>(Func<Task<TResponse>> sendAsync) {
int timeout = 15;
// here you create Task which represents ongoing request
var sendTask = sendAsync();
// Task which will complete after specified amount of time, in milliseconds
// which means your timeout should be 15000 (for 15 seconds), not 15
var delay = Task.Delay(timeout);
// wait for any of those tasks to complete, returns task that completed first
var taskThatCompletedFirst = await Task.WhenAny(sendTask, delay);
if (taskThatCompletedFirst == sendTask) {
// if that's our task and not "delay" task - we are fine
// await it so that all exceptions if any are thrown here
// this will _not_ cause it to execute once again
return await sendTask;
}
else {
// "delay" task completed first, which means 15 seconds has passed
// but our request has not been completed
throw new Exception("time out!!!");
}
}
Request is sent twice because sendAsync is a Func returning Task, different on each call. You call it first under Task.WhenAny() and repeat in operator return await sendAsync().
To avoid this duplicated call you should save a task to variable and pass that task to both calls:
public async Task<TResponse> SendRequestAsync<TResponse>(Func<Task<TResponse>> sendAsync)
{
int timeout = 15;
var task = sendAsync();
if (await Task.WhenAny(task, Task.Delay(timeout) == task))
{
return await task;
}
else
{
throw new Exception("time out!!!");
}
}
await on completed task will just return its result without rerunning the task.
I have an ASP.NET MVC application which needs to check if something exists at 3 remote API servers. The application passes an ID to each API and it returns either true or false. The code looks like this.
public class PingController
{
public async Task<bool> IsFound(int id)
{
var servers = new ['a.com', b.com', 'c.com'];
var result = await foundAtServers(id, servers);
return result;
}
private async Task<bool> foundAtServers(int id, string[] servers)
{
var tasks = from server in servers
select checkServer(id, server);
return await.Task.WhenAll(tasks.ToArray());
}
private async Task<bool> checkServer(id, server)
{
var request = new HttpRequestMessage(HttpMethod.Get, server+"/api/exists"+id);
var client = new HttpClient();
var task = await client.SendAsync(request);
var response = await task.Content.ReadAsStringAsync();
return bool.Parse(response);
}
}
This code currently checks all 3 APIs asynchronously but will wait until ALL of the HttpClient calls have completed before the MVC Action can return.
As soon as one API returns true I want to immediately return true on the Action, rather than wait for the other tasks to complete.
The C# Task class has .WaitAll and .WaitAny, but these won't work either. As I need to cancel the other HttpClient request, I presume I need to use a CancellationToken but I don't know how to use it with this structure.
Cheers.
If you want to immediately return, you can use Task.WhenAny instead of Task.WhenAll. This won't cancel the on-going tasks, but it will enable you to return as soon as possible:
private async Task<bool> FoundAtServersAsync(int id, string[] servers)
{
var tasks = (from server in servers
select checkServer(id, server)).ToList();
while (tasks.Count > 0)
{
var finishedTask = await Task.WhenAny(tasks);
if (finishedTask.Result)
{
return finishedTask.Result;
}
tasks.Remove(finishedTask);
}
return false;
}
This will discard the other tasks. This means that if any exception is thrown inside one of them, it will be swallowed.
Edit:
If you care about actually canceling the other tasks, consider passing your CancellationToken to the overload of SendAsync which takes one, and calling CancellationTokenSource.Cancel once a value is received. Note this will mean you'll also need to handle the OperationCanceledException they will throw.
If they don't matter, i'd simply discard them as above.
This problem is made easier by using the following method to take a sequence of tasks and order them based on when they are completed.
public static IEnumerable<Task<T>> Order<T>(this IEnumerable<Task<T>> tasks)
{
var taskList = tasks.ToList();
var taskSources = new BlockingCollection<TaskCompletionSource<T>>();
var taskSourceList = new List<TaskCompletionSource<T>>(taskList.Count);
foreach (var task in taskList)
{
var newSource = new TaskCompletionSource<T>();
taskSources.Add(newSource);
taskSourceList.Add(newSource);
task.ContinueWith(t =>
{
var source = taskSources.Take();
if (t.IsCanceled)
source.TrySetCanceled();
else if (t.IsFaulted)
source.TrySetException(t.Exception.InnerExceptions);
else if (t.IsCompleted)
source.TrySetResult(t.Result);
}, CancellationToken.None,
TaskContinuationOptions.PreferFairness,
TaskScheduler.Default);
}
return taskSourceList.Select(tcs => tcs.Task);
}
With this you can write:
public static async Task<bool> WhenAny(this IEnumerable<Task<bool>> tasks)
{
foreach (var task in tasks.Order())
if (await task)
return true;
return false;
}
You could wait for the first task to complete - if it's successful, return true immediately. Otherwise, wait for the next one to complete, and so on and so forth.
private async Task<bool> foundAtServers(int id, string[] servers)
{
var tasks = servers.Select(server => checkServer(id, server))
.ToList();
while(tasks.Any())
{
var task = await Task.WhenAny(tasks);
if(task.Result)
return true;
tasks.Remove(task);
}
return false;
}
I am using the DropNet API for connecting to DropBox. I am struggling around async/await concepts though.
I have a method is calling the api GetTokenAsync. The return type is void, and there is a success and failure action to callback.
public async Task<GetTokenResult> GetAuthorizationUrl()
{
var result = new GetTokenResult();
_dropNetClient.GetTokenAsync(
login =>
{
result.Url = _dropNetClient.BuildAuthorizeUrl(_authorizationCallback.ToString());
result.Success = true;
},
exception =>
{
result.Error = exception.ToDiagnosticString();
result.Success = false;
}
);
return result;
}
The problem? I am thinking that changing the return type to just GetTokenResult may return faster than the actions will, therefore my results will never get set. I cannot await the async method as it returns void.
This is the one concept around async/await that I cannot wrap my head around.
You might want to consider using a TaskCompletionSource:
public Task<GetTokenResult> GetAuthorizationUrl()
{
var tcs = new TaskCompletionSource<GetTokenResult>();
_dropNetClient.GetTokenAsync(
login => tcs.SetResult(new GetTokenResult {
Url = _dropNetClient.BuildAuthorizeUrl(
_authorizationCallback.ToString()),
Success = true
},
exception => tcs.SetResult(new GetTokenResult {
Error = exception.ToDiagnosticString(),
Success = true
});
return tcs.Task;
}
The returned task will only complete when the GetTokenAsync operation completes (via one of the callbacks), and you can await it from an async method.
I would personally use SetException instead of SetResult on failure though - so that if you await the returned task, it will throw the appropriate failure on an exception, rather than just setting the value differently. It's more idiomatically .NET-like.
EDIT: You could then change the code to return Task<string> instead:
public Task<string> GetAuthorizationUrl()
{
var tcs = new TaskCompletionSource<string>();
_dropNetClient.GetTokenAsync(
login => tcs.SetResult(_dropNetClient.BuildAuthorizeUrl
_authorizationCallback.ToString()),
exception => tcs.SetException(exception));
return tcs.Task;
}
Now the exception would be propagated within the task itself - if you await a task which faults, the exception is thrown at that point. No need for extra properties, etc - it's much closer to how you'd write the corresponding synchronous code.
task1:
public Task SendMail(string email)
{
...
}
task2:
public Task<int> SaveToDB(User user)
{
...
}
What I need to do:
when task1 fail, should return a "send failure" infomation;
when task1 success, task2 begin.
when task2 fail, should return a "save failure" infomation.
when task2 success, should return a "save success" infomation.
Please help me find a solution:
public Task<string> SendAndSave(User user){
var task1 = SendMail(user.Email);
var task1Failed = task1.ContinueWith(t =>
{
var e = task1.Exception;
return "Send Failed";
},
TaskContinuationOptions.OnlyOnFaulted |
TaskContinuationOptions.ExecuteSynchronously);
var task2 = task1.ContinueWith(t =>
{
var save = SaveToDB(user);
try
{
int result = save.Result;
return "Save Succeeded";
}
catch(AggregateException ae)
{
return "Save Failed";
}
},
TaskContinuationOptions.NotOnFaulted);
return Task.Factory.ContinueWhenAny(new[] {task1Failed, task2}, t => t.Result);
}
When I run it I receive an error.
I invoked it as:
var result = SendAndSave(user).Result;
the error occured at:
public Task<string> SendAndSave(User user)
{
...
return Task.Factory.ContinueWhenAny(new[] {task1Failed, task2}, t => t.Result); //Here: A task was cancelled
}
After debugging it , I have two questions:
Q1: after task1, task1Failed, task2 are created, the value of each
CreationOption property is "none" although its Status is
"WaitingForActivation". It seems all the continuation options are
invalid.
Q2: For the task of Task.Factory.ContinueWhenAny(new[] {task1Failed,
task2}, t => t.Result), let's name it "factoryTask", the
ContinueWhenAny seems invalid. After put a breakpoint in the internal
of each of task1Failed, task2 and factoryTask, I can see occasionally
the breakpoint in the factoryTask was hitted firstly, though it
should be hitted after either task1Failed or task2 had completed.
Anybody can help ? Thanks.
Although I don't fully understand your questions, I understand the issue here, basically you will always have either task1Failed or task2 completes but not both, and the other one will be cancelled!
So in WaitOnAny call, if it finds one of the tasks is already cancelled, it will crash, and this really depends on the order of the tasks parameter, so in your example above you pass task1failed first, so if this task completeted ContinueWhenAny will work fine because the first task it checks is already completed so it doesn't check the other one, but if task1Failed didn't run so it is in a cancelled state, ContinueWhenAny will throw!
To avoid this, you need to use TaskCompletetionSource instead, and the task which runs, will set the value, then at the end you return tcs.Task.Result
public Task<string> SendAndSave(User user){
var tcs = new TaskCompletionSource<string>();
var task1 = SendMail(user.Email);
var task1Failed = task1.ContinueWith(t =>
{
var e = task1.Exception;
tcs.TrySetResult("Send Failed");
},
TaskContinuationOptions.OnlyOnFaulted |
TaskContinuationOptions.ExecuteSynchronously);
var task2 = task1.ContinueWith(t =>
{
var save = SaveToDB(user);
try
{
int result = save.Result;
tcs.TrySetResult("Save Succeeded");
}
catch(AggregateException ae)
{
tcs.TrySetResult("Save Failed");
}
},TaskContinuationOptions.NotOnFaulted);
return tcs.Task.Result;
}
I have the following method:
public async Task ExecuteAsync()
{
Task<IEnumerable<Comment>> gettingComments = RetrieveComments();
Dictionary<string, ReviewManager> reviewers = ConfigurationFacade.Repositories.ToDictionary(name => name, name => new ReviewManager(name));
IEnumerable<Comment> comments = await gettingComments;
Parallel.ForEach(reviewers, (reviewer) => {
Dictionary<Comment, RevisionResult> reviews = reviewer.Value.Review(comments);
int amountModerated = ModerateComments(reviews.Where(r => r.Value.IsInsult), "hide");
});
}
My ModerateComments method looks like the following:
private Task<int> ModerateComments(IEnumerable<Comment> comments, string operation)
{
return Task.Factory.StartNew(() =>
{
int moderationCount = 0;
Parallel.ForEach(comments, async (comment) =>
{
bool moderated = await ModerateComment(comment, operation); //Problem here
if(moderated)
moderationCount++;
}
return moderationCount;
};
}
And finally:
private async Task<bool> ModerateComment(Comment comment, string operation, string authenticationToken = null)
{
if(comment == null) return false;
if(String.IsNullOrWhiteSpace(authenticationToken))
authenticationToken = CreateUserToken(TimeSpan.FromMinutes(1));
string moderationEndpoint = ConfigurationFacade.ModerationEndpoint;
using(HttpRequestMessage request = new HttpRequestMessage())
{
request.Method = HttpMethod.Post;
request.RequestUri = new Uri(moderationEndpoint);
using(HttpResponseMessage response = await _httpClient.SendAsync(request)) //Problem here
{
if(!response.IsSuccessStatusCode)
{
if(response.StatusCode == HttpStatusCode.Unauthorized)
return await ModerateComment(comment, operation, null); //Retry operation with a new access token
else if(response.StatusCode == HttpStatusCode.GatewayTimeout)
return await ModerateComment(comment, operation, authenticationToken); //Retry operation
return false;
}
}
}
return true;
}
I'm having a strange problem at runtime. All the above code is working fine except when it reaches the line:
using(HttpResponseMessage response = await _httpClient.SendAsync(request)) {
//...
}
When I debug my application, this instruction is executed but just after that, it does not throw any exception, nor return anything, it just finish executing and I am derived to the next statement on the Parallel.ForEach loop.
It is really hard to explain so I'll post some images:
All good so far, I reach the following line of code:
The execution keeps going well and I reach the call to the Moderation API
Even if I press F10 (Next statement) in the debugger, the execution flow jumps to the next loop in the Parallel.ForEach loop.
As you can see I have breakpoints in the try-catch just i ncase any exception is thrown, but the breakpoint is never activated, neither is activated the breakpoint in if(moderacion) commentCount++.
So what happens here? Where did my execution flow went? It just dissapears after sending the POST request to the API.
After continuing the execution, all the elements in the enumerable do the same jump, and therefore, my commentCount variable ends up being equal to 0
You don't need Parallel.ForEach or Task.Factory.StartNew to do IO bound work:
private async Task<int> ModerateCommentsAsync(IEnumerable<Comment> comments, string operation)
{
var commentTasks = comments.Select(comment => ModerateCommentAsync(comment, operation));
await Task.WhenAll(commentTasks);
return commentTasks.Count(x => x.Result);
}
Common practice is to add the Async postfix to an async method.
Excellent description for a common problem. Parallel.ForEach does not support async lambdas. async methods return once they hit the first await that would need to block. This happens when you issue the HTTP request.
Use one of the common patterns for a parallel async foreach loop.