From what I've seen about using the Async CTP with the event asynchronous pattern, the code I have here should work fine, with var result1 = await tcs1.Task blocking until clientGetFileList.GetCompleted fires. However, what ends up happening is that I get bounced back to GetRestoreStream, on return GetRestoreStreamAwait().Result which never does return -- instead, my app pretty much locks up on me.
Can someone please explain to me what it is I am doing wrong?
protected override Stream GetRestoreStream()
{
if (SkyDriveFolderId != null)
return GetRestoreStreamAwait().Result;
return Stream.Null;
}
private async Task<Stream> GetRestoreStreamAwait()
{
LiveConnectClient clientGetFileList = new LiveConnectClient(_session);
TaskCompletionSource<LiveOperationCompletedEventArgs> tcs1 = new TaskCompletionSource<LiveOperationCompletedEventArgs>();
EventHandler<LiveOperationCompletedEventArgs> d1 = (o, e) => { tcs1.TrySetResult(e); };
clientGetFileList.GetCompleted += d1;
clientGetFileList.GetAsync(SkyDriveFolderId + "/files");
var result1 = await tcs1.Task;
clientGetFileList.GetCompleted -= d1;
// ... method continues for a while
}
Update: This bit of code seems to move through all the way, but task.Start() tosses off an InvalidOperationException so I never actually get the stream at the end. Wrapping it in a try/catch doesn't change anything, either -- without the try/catch the InvalidOperationException is caught further up the stack while the operation runs happily ignorant of the fact its result will never be used; with it, task.Result freezes things just as surely as the code above.
protected override Stream GetRestoreStream()
{
if (SkyDriveFolderId != null)
{
var task = GetRestoreStreamImpl();
task.Start();
return task.Result;
}
return Stream.Null;
}
private async Task<Stream> GetRestoreStreamImpl()
{
var getResult = await GetTaskAsync(SkyDriveFolderId + "/files");
List<object> data = (List<object>)getResult["data"];
foreach (IDictionary<string, object> dictionary in data)
{
if (dictionary.ContainsKey("name") && (string)dictionary["name"] == BackupFileName)
{
if (dictionary.ContainsKey("id"))
{
SkyDriveFileId = (string)dictionary["id"];
break;
}
}
}
if (String.IsNullOrEmpty(SkyDriveFileId))
{
MessageBox.Show("Restore failed: could not find backup file", "Backup", MessageBoxButton.OK);
return Stream.Null;
}
return await DownloadTaskAsync(SkyDriveFileId + "/content");
}
private Task<IDictionary<string,object>> GetTaskAsync(string path)
{
var client = new LiveConnectClient(_session);
var tcs = new TaskCompletionSource<IDictionary<string, object>>();
client.GetCompleted += (o, e) =>
{
if (e.Error != null)
tcs.TrySetException(e.Error);
else if (e.Cancelled)
tcs.TrySetCanceled();
else
tcs.TrySetResult(e.Result);
};
client.GetAsync(path);
return tcs.Task;
}
private Task<Stream> DownloadTaskAsync(string path)
{
var client = new LiveConnectClient(_session);
var tcs = new TaskCompletionSource<Stream>();
client.DownloadCompleted += (o, e) =>
{
if (e.Error != null)
tcs.TrySetException(e.Error);
else if (e.Cancelled)
tcs.TrySetCanceled();
else
tcs.TrySetResult(e.Result);
};
client.DownloadAsync(path);
return tcs.Task;
}
You are misunderstanding the way that async/await works. Basically, your code is blocking at the var result1 and below. However, what await allows is for the code that called the async method (GetRestoreStream in this case) to be returned to as soon as a long running task* with await in front of it is called. If you were not reliant on the .Result, then your GetRestoreStream method would complete. However, since you require the result, then your GetRestoreStream method becomes synchronous while it waits for GetRestoreStreamAwait to complete. I will add some visuals shortly.
Here is some sample code flow:
-GetRestoreStream calls GetRestoreStreamAwait
---GetRestoreStreamAwait calls an async task
-GetRestoreStreamAwait returns to GetRestoreStream with a pending result
-GetRestoreStream can do anything it wants, but if it calls for the pending result, it will block
---GetRestoreStreamAwait finally finishes its async task and continues through its code, returning a result
-Any code in GetRestoreStream that was waiting for the result receives the Result
This is not the best graphical representation, hopefully it helps explain it a little though. The thing to notice is that the code flow is not what you are used to due to the nature of async
So, my guess is that your app locks up only because you are trying to access the Result that is not available yet, and all you should have to do is wait for the tcs1.Task to complete. If you want to avoid locking up, then you will need to nest the call so that GetRestoreStream is also an async method. However, if the result is what you are ultimately looking for, then you will need to wait for the return, or simply set up a callback like you normally would for the async pattern
*Notice that I said long running task because the compiler will not waste time rewriting code that is already completed (if it is indeed completed by the time the await is called)
UPDATE...try this
protected override Stream GetRestoreStream()
{
if (SkyDriveFolderId != null)
return GetRestoreStreamAwait().Result;
return Stream.Null;
}
private async Task<Stream> GetRestoreStreamAwait()
{
try
{
LiveConnectClient clientGetFileList = new LiveConnectClient(_session);
TaskCompletionSource<LiveOperationCompletedEventArgs> tcs1 = new TaskCompletionSource<LiveOperationCompletedEventArgs>();
EventHandler<LiveOperationCompletedEventArgs> d1 =
(o, e) =>
{
try
{
tcs1.TrySetResult(e);
}
catch(Exception ex)
{
tcs1.TrySetResult(null);
}
};
clientGetFileList.GetCompleted += d1;
clientGetFileList.GetAsync(SkyDriveFolderId + "/files");
var result1 = await tcs1.Task;
clientGetFileList.GetCompleted -= d1;
// ... method continues for a while
}
catch(Exception ex)
{
return null;
}
}
Related
I want to write a code which connects to a Server, but times out after 10 seconds. I thought of Task.WhenAny() would do the trick. So far, I have come with the following code:
private async Task<bool> tryConnect() //Times out after 10 seconds
{
bool connected = false;
bool alreadyWentOut = false;
await Task.WhenAny(
Task.Run(async() =>
{
try
{
return connected = await CrossInAppBilling.Current.ConnectAsync();
}
finally
{
if (alreadyWentOut) await CrossInAppBilling.Current.DisconnectAsync();
}
}),
Task.Delay(10000)
);
alreadyWentOut = true;
return connected;
}
Is there a smell in the current code? In my IDE (using jetbrains rider) it is giving me the following warning
If there is a code small, how do implement this better?
Essentially what you are trying to do is to attach a DisconnectAsync continuation, after the ConnectAsync has timed-out and has become a fire-and-forget task. Here is an explicit way to do it, that uses the new (.NET 6) WaitAsync API:
private async Task<bool> TryConnect(int timeoutSeconds = 10)
{
var connectTask = CrossInAppBilling.Current.ConnectAsync();
try
{
return await connectTask.WaitAsync(TimeSpan.FromSeconds(timeoutSeconds));
}
catch (TimeoutException)
{
_ = DisconnectAfterConnectAsync(); // Fire and forget
return false;
}
async Task DisconnectAfterConnectAsync()
{
await connectTask;
await CrossInAppBilling.Current.DisconnectAsync();
}
}
My instinct says that you'll find more problems down the route, related with the ambient CrossInAppBilling.Current property, and with subsequent attempts to TryConnect while a fire-and-forget DisconnectAfterConnectAsync is still in-flight. Fire and forget is a recipe for problems.
When performing a long running operation I noticed that i could kickstart a long running sub-operation right off the start line and do other stuff while it fetches results from caches/databases.
The given operation is:
public async Task<Fichaclis> Finalize()
{
using (TransactionScope transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
transactionTimer.Start();
var agendasTransitionTask = ExecuteAgendas();
... DO ALOT OF SYNC OPERATIONS ...
await agendasTransitionTask;
transaction.Complete();
}
}
private Task ExecuteAgendas()
{
return ags.GetAgendas().ContinueWith((prev) =>
{
var currentAgendas = prev.Result;
foreach (var item in currentAgendas)
{
... DO QUICK SYNC STUFF...
}
return ags.BulkEditAgenda(currentAgendas);
});
}
GetAgendas is a method used all over with the following signature:
public async Task<List<Agendas>> GetAgendas()
because it's widely used, i believe the problem is not there. As for BulkEditAgenda:
public async Task BulkEditAgenda(IEnumerable<Agendas> agendas)
{
if (agendas == null || agendas.Count() == 0)
{
return;
}
var t1 = AddOrUpdateCache(agendas);
var t2 = Task.Factory.StartNew(() =>
{
try
{
foreach (var item in agendas)
{
EditNoReconnection(item);
}
Save();
}
catch (Exception ex)
{
//log
throw;
}
});
await Task.WhenAll(t1, t2);
}
EditNoReconnect and Save are both sync methods.
private Task AddOrUpdateCache(IEnumerable<Agendas> agendas)
{
var tasks = new List<Task>();
foreach (var item in agendas)
{
tasks.Add(TryGetCache(item)
.ContinueWith((taskResult) =>
{
...DO QUICK SYNC STUFF...
})
);
}
return Task.WhenAll(tasks);
}
TryGetCache is also a widely used method, so I think it's safe... it's signature is private Task<AgendasCacheLookupResult> TryGetCache(
So, resuming the issue at hand: For a small set of items do in the sync session of the Finalize method the command transaction.Complete() is execute before Save() (inside BulkEditAgendas). For a regular or large amount of items, it works as expected.
This means that i'm not chaining the Tasks correctly, or my understanding of how Async/Await + Tasks/ContinueWith works is fundamentally incorrect. Where am I wrong?
The problem is most likely here:
private Task ExecuteAgendas()
{
return ags.GetAgendas().ContinueWith((prev) =>
{
var currentAgendas = prev.Result;
foreach (var item in currentAgendas)
{
... DO QUICK SYNC STUFF...
}
return ags.BulkEditAgenda(currentAgendas);
});
}
First, what you return from this is the continuation task (result of ContinueWith). But body of ContinueWith ends when you do
return ags.BulkEditAgenda(currentAgendas);
So body of continuation ends potentially before BulkEditAgenda task is completed (you don't wait in any way for completion of BulkEditAgenda). So this line
await agendasTransitionTask;
Returns while BulkEditAgenda is still in progress. To clarify even more, note that what is returned from ExecuteAgendas is Task<Task> and result of await agendasTransitionTask is Task which represents your running BulkEditAgenda.
To fix, just use async\await like you do everywhere else:
private async Task ExecuteAgendas() {
var currentAgengas = await ags.GetAgendas();
foreach (var item in currentAgendas) {
// do stuff
}
await ags.BulkEditAgenda(currentAgendas);
}
I have a function (below) that I retrieve data from an API. If I set a breakpoint at the line that deserializes it, then I can see that it is populated with data which is great.
When I continue on, it goes into the second function (below) and it throws an error. The error says next to it Not yet computed, and therefore throwing an exception.
When I do it with a small list it works just fine (I presume its cos it's a small set of data).
How is this possible when I'm using ContinueWith (waiting for the task to complete)?
public static async Task<Data> GetAllCardsInSet(string setName)
{
setName = WebUtility.UrlEncode(setName);
var correctUri = Path.Combine(ApiConstants.YugiohGetAllCardsInSet, setName);
Console.WriteLine();
using (var httpClient = new HttpClient())
{
var response =
await httpClient.GetAsync(correctUri);
var result = await response.Content.ReadAsStringAsync();
var cardData = JsonConvert.DeserializeObject<CardSetCards>(result);
for (int i = 0; i < cardData.Data.Cards.Count; i++)
{
cardData.Data.Cards[i] = FormatWords(cardData.Data.Cards[i]);
}
return cardData.Data;
}
}
private void GetYugiohCardsAndNavigate(string name)
{
var cardSetData = YugiohRequester.GetAllCardsInSet(_selectedCardSet.Name).ContinueWith((result) =>
{
//var cards = await YugiohRequester.GetAllCardsInSet(_selectedCardSet.Name);
try
{
this.mainPage.NavigateToYugiohCardListPage(result.Result);
}
catch (Exception e)
{
HelperFunctions.ShowToastNotification("Trading Card App", "Sorry, we could not fetch this set");
}
});
}
Your GetAllCardsInSet method no need to change.
But using of this method can be refactored.
Method GetAllCardsInSet return Task and you not observed the completion of the this Task.
You need to check is Task completes succesfully, easiest approach to use await keyword. Awaiting task will unwrapp returned value or throw exception if task completed with exception.
For using async/await in the GetYugiohCardsAndNavigate change method signature to aynchronous and returning Task
private async Task GetYugiohCardsAndNavigate(string name)
{
try
{
var cardSetData = await YugiohRequester.GetAllCardsInSet(_selectedCardSet.Name);
this.mainPage.NavigateToYugiohCardListPage(cardSetData);
}
catch (Exception e)
{
HelperFunctions.ShowToastNotification("Trading Card App",
"Sorry, we could not fetch this set");
}
}
you called an async method in a sync method without Wait. It should have been done like:
YugiohRequester.GetAllCardsInSet(_selectedCardSet.Name).ContinueWith((result) =>
{
//var cards = await YugiohRequester.GetAllCardsInSet(_selectedCardSet.Name);
try
{
this.mainPage.NavigateToYugiohCardListPage(result.Result);
}
catch (Exception e)
{
HelperFunctions.ShowToastNotification("Trading Card App", "Sorry, we could not fetch this set");
}
}).Wait();
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;
}
}
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.