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;
}
}
Related
I'm trying to use System.Net.Http.HttpClient to return the results of a call to a webpage, so that it implements a POST request. I don't really want to perform this asynchronously. My requirement is to wait until all the data is returned before continuing, so ideally I want synchronous method. However, sadly, it is not possible to just use HttpClient that way.
I've declared the following method, which is asynchronous, which takes a URL and key-value pairs to populate $_POST in the PHP:
private async Task<string> PostRequest(string cUrl, params string[] aParams)
{
HttpClient oClient;
Dictionary<string, string> oArgs;
int iA;
FormUrlEncodedContent oContent;
HttpResponseMessage oResponse;
// check we have an event number of parameters
if ((aParams.GetUpperBound(0) % 2) != 1) throw new Exception(
"Non-even number of parameters passed. Parameters are key-value pairs.");
// put the parameters into a dictionary
oArgs = new Dictionary<string, string>();
for (iA = 0; iA < aParams.GetUpperBound(0); iA += 2)
oArgs.Add(aParams[iA], aParams[iA + 1]);
oClient = new HttpClient();
oContent = new FormUrlEncodedContent(oArgs);
oClient.Timeout = new TimeSpan(0, 0, 10);
oResponse = await oClient.PostAsync(cUrl, oContent);
return await oResponse.Content.ReadAsStringAsync();
}
Now, annoyingly this has to be an asynchonous method. Ho hum. Ideally, I'd like to call it thus:
private void button2_Click(object sender, EventArgs e)
{
var cResult = await PostRequest("http://mywebsite.com/mypage.php",
"MagicToken", "12345",
"Method", "GetSomeData");
txt.Text = cResult.ToString();
}
But I have the compile time error:
The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'.
What I'm doing above is (obviously) a test. In reality the button that kicks this off is a "Next >" in a wizard. It will use the results to populate a structure with data that other code in the wizard then accesses. I don't the above to occur asynchronously as I don't want other code touching that structure until it is populated.
My question is, how can I wrap a call to PostRequest so that I can wait for all the results to come in (some sort of ...while still processing...wait... loop) and then just return the results of the call, and use that without having to bubble async declarations up through my code?
As a second question, if I have to declare my cmdNext_Click as async, what happens if the user clicks it twice? I specifically want the UI thread to stop until the data is returned and processed.
Edit:
I've tried creating a wrapper function (which is non-async) thus:
private bool PostRequest2(string cUrl, ref string cResponse, params string[] aParams)
{
// This posts a request to the URL, using the parameters passed in aArgs. The response is returned in cResponse.
// cUrl - the URL to POST to
// cResponse - the response returned
// aParams - an even number of parameters, which are key-value pairs. The first of each pair is the name of the item. The second is its value.
int iWaitCount;
try
{
var response = PostRequest(cUrl, aParams);
Console.WriteLine(response);
iWaitCount = 0;
while (!response.IsCompleted)
{
Console.WriteLine("iWaitCount = " + iWaitCount.ToString());
Console.WriteLine("Status = " + response.Status.ToString());
response.Wait(500);
iWaitCount++;
}
cResponse = response.Result;
return true;
}
catch (Exception ex)
{
_g.Errs.Raise(ex);
return false;
}
}
This compiles correctly, but sits in the wait loop indefinitely with response.Status = WaitingForActivation.
There has to be a way to wrap an asynchronouns function in a synchrnous one. The alternative is to have to change all the return types (which are mostly bool - true on success) to Task, which I cannot then use in conditional statements - I have to await them instead. I've realised that this is the fundimental question, and this is a duplicate of: How to call asynchronous method from synchronous method in C#? which refers to await being a zombie virus that infects your code; this appears to be the case.
You can make your button void async (I would maybe return Task instead of void though)
await should mean that your method waits for the PostAsync call to complete.
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/await
The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes.
So this is essentially a synchronous call.
Now if you really don't want that void to be async, here's what I can remember off the top of my head:
In .NET 5+, you can use HttpClient.Send which is synchronous. (takes HttpRequestMessage)
https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.send?view=net-5.0
Otherwise, you would need to do a .Result if you wanted to get the response. This type of consumption of async methods has been frowned upon in my experience.
Disable button2 until the operation is completed and use async inside the button2 click event.
change button2_Click to :
private async void button2_Click(object sender, EventArgs e)
{
button2.Enabled = false;
var cResult = await PostRequest("http://mywebsite.com/mypage.php",
"MagicToken", "12345",
"Method", "GetSomeData");
txt.Text = cResult.ToString();
button2.Enabled = true;
}
After much reading, and thank you to those above, I've got a working method now. It's nor perfect, but it works in this scenario where I'm calling one async method at a time, and wait processing to stop until it returns.
PostRequest above works correctly, but it must be declared async and called with await. Within my app, I have a variety of callers of it, which must also be declared async and use await when they call it. An example is:
private async Task<bool> ReadProductPrice()
{
string cCsv = "";
try
{
cProductCode = scSubscriptionType.GetSelectedKey().ToString();
var oResponse = await PostRequest("http://mywebsite.com/mywebpage.php",
"MagicToken", "12345",
"Query", "GetProductPrice",
"ProductCode", cProductCode);
if (oResponse == null) throw new Exception("Could not acquire product price from server. (1)");
cCsv = oResponse.ToString();
moProductPrice = new Dataset(_g);
if (!moProductPrice.ReadFromCsv(cCsv)) throw new Exception("Could not decode server response.");
if (moProductPrice.RecordCount != 1) throw new Exception("Could not acquire product price from server. (2)");
return true;
}
catch (Exception ex)
{
_g.Errs.Raise(ex);
return false;
}
}
This works correctly and populates moProductPrice with the data returned from PostRequest. However, it is async.
I've create a wrapper function thus:
private bool ReadProductPrice2()
{
Task<bool> oTask;
frmWaitForTaskCompletion frm;
try
{
oTask = ReadProductPrice();
frm = new frmWaitForTaskCompletion();
frm.WaitForTaskCompletion(oTask, "Waiting for product price from server...");
return true;
}
catch (Exception ex)
{
_g.Errs.Raise(ex);
return false;
}
}
This passes the Task<bool> returned from ReadProductPrice through to a form. The form contains a Label and a Timer, named lblMessage and tmr, containing the following code:
public partial class frmWaitForTaskCompletion : Form
{
private Task _task;
public frmWaitForTaskCompletion()
{
InitializeComponent();
}
public void WaitForTaskCompletion<TResult>(Task<TResult> oTask, string cMessage)
{
_task = oTask;
lblMessage.Text = cMessage;
this.ShowDialog();
return;
}
private void frmWaitForTaskCompletion_Load(object sender, EventArgs e)
{
tmr.Enabled = true;
}
private void tmr_Tick(object sender, EventArgs e)
{
if (_task.Status == TaskStatus.RanToCompletion)
this.Close();
}
}
The timer is set to an Interval of 1000 so that it shows for enough time for the user to recognise that a popup has occurred and to scan the message.
Ideally, I would like to replace the call to the wait form with this:
while (oTask.Status != TaskStatus.RanToCompletion) Thread.Sleep(100);
And I don't actually understand why this doesn't now work, but recognise that it doesn't; code never continues after this point, despite the fact that the wait form is effectively performing the same check.
In this way, I'm able to stop the await/async propogating up my call stack indefinitely; IMO should be the compiler's job to sort that out, not mine, and it signifcantly breaks the concept of encapsulation. I dislike the fact that I need to show a wait form for a short while, but in this context the user should be aware of the communication that is going on, so it's an ok solution.
You can try doing something like this
private void button2_Click(object sender, EventArgs e)
{
PostRequest(
"http://mywebsite.com/mypage.php",
"MagicToken",
"12345",
"Method",
"GetSomeData"
)
.ContinueWith(async request => {
var cResult = await request;
txt.Text = cResult.ToString();
})
.Wait();
}
I've written a class that asynchronously pings a subnet. It works, however, the number of hosts returned will sometimes change between runs. Some questions:
Am I doing something wrong in the code below?
What can I do to make it work better?
The ScanIPAddressesAsync() method is called like this:
NetworkDiscovery nd = new NetworkDiscovery("192.168.50.");
nd.RaiseIPScanCompleteEvent += HandleScanComplete;
nd.ScanIPAddressesAsync();
namespace BPSTestTool
{
public class IPScanCompleteEvent : EventArgs
{
public List<String> IPList { get; set; }
public IPScanCompleteEvent(List<String> _list)
{
IPList = _list;
}
}
public class NetworkDiscovery
{
private static object m_lockObj = new object();
private List<String> m_ipsFound = new List<string>();
private String m_ipBase = null;
public List<String> IPList
{
get { return m_ipsFound; }
}
public EventHandler<IPScanCompleteEvent> RaiseIPScanCompleteEvent;
public NetworkDiscovery(string ipBase)
{
this.m_ipBase = ipBase;
}
public async void ScanIPAddressesAsync()
{
var tasks = new List<Task>();
m_ipsFound.Clear();
await Task.Run(() => AsyncScan());
return;
}
private async void AsyncScan()
{
List<Task> tasks = new List<Task>();
for (int i = 2; i < 255; i++)
{
String ip = m_ipBase + i.ToString();
if (m_ipsFound.Contains(ip) == false)
{
for (int x = 0; x < 2; x++)
{
Ping p = new Ping();
var task = HandlePingReplyAsync(p, ip);
tasks.Add(task);
}
}
}
await Task.WhenAll(tasks).ContinueWith(t =>
{
OnRaiseIPScanCompleteEvent(new IPScanCompleteEvent(m_ipsFound));
});
}
protected virtual void OnRaiseIPScanCompleteEvent(IPScanCompleteEvent args)
{
RaiseIPScanCompleteEvent?.Invoke(this, args);
}
private async Task HandlePingReplyAsync(Ping ping, String ip)
{
PingReply reply = await ping.SendPingAsync(ip, 1500);
if ( reply != null && reply.Status == System.Net.NetworkInformation.IPStatus.Success)
{
lock (m_lockObj)
{
if (m_ipsFound.Contains(ip) == false)
{
m_ipsFound.Add(ip);
}
}
}
}
}
}
One problem I see is async void. The only reason async void is even allowed is only for event handlers. If it's not an event handler, it's a red flag.
Asynchronous methods always start running synchronously until the first await that acts on an incomplete Task. In your code, that is at await Task.WhenAll(tasks). At that point, AsyncScan returns - before all the tasks have completed. Usually, it would return a Task that will let you know when it's done, but since the method signature is void, it cannot.
So now look at this:
await Task.Run(() => AsyncScan());
When AsyncScan() returns, then the Task returned from Task.Run completes and your code moves on, before all of the pings have finished.
So when you report your results, the number of results will be random, depending on how many happened to finish before you displayed the results.
If you want make sure that all of the pings are done before continuing, then change AsyncScan() to return a Task:
private async Task AsyncScan()
And change the Task.Run to await it:
await Task.Run(async () => await AsyncScan());
However, you could also just get rid of the Task.Run and just have this:
await AsyncScan();
Task.Run runs the code in a separate thread. The only reason to do that is in a UI app where you want to move CPU-heavy computations off of the UI thread. When you're just doing network requests like this, that's not necessary.
On top of that, you're also using async void here:
public async void ScanIPAddressesAsync()
Which means that wherever you call ScanIPAddressesAsync() is unable to wait until everything is done. Change that to async Task and await it too.
This code needs a lot of refactoring and bugs like this in concurrency are hard to pinpoint. My bet is on await Task.Run(() => AsyncScan()); which is wrong because AsyncScan() is async and Task.Run(...) will return before it is complete.
My second guess is m_ipsFound which is called a shared state. This means there might be many threads simultaneously reading and writing on this. List<T> is not a data type for this.
Also as a side point having a return in the last line of a method is not adding to the readability and async void is a prohibited practice. Always use async Task even if you return nothing. You can read more on this very good answer.
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();
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;
}
}
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.