Using async instead of Task.Run() - c#

I have the following piece of code:
private void btnAction_Click(object sender, RoutedEventArgs e)
{
/** Clear the results field */
txtResult.Text = "";
/** Disable the button and show waiting status */
btnAction.IsEnabled = false;
lblStatus.Text = "Wait...";
/** Get input from the query field */
string input = query.Text;
/** Run a new task */
Task.Run(() => {
// calling a method that takes a long time (>3s) to finish and return
var attempt = someLibrary.doSomethingWith(input);
// return the result to the GUI thred
this.Dispatcher.Invoke(() =>
{
if (attempt.ContainsKey("success"))
{
if (attempt["success"] == true)
{
txtResult.Text = "Success! The door is: " + (attempt["is_open"] ? "open" : "closed");
lblStatus.Text = "";
}
else
{
lblStatus.Text = "Error! The service says: " + attempt["errorMessage"];
}
}
else
{
MessageBox.Show("There was a problem getting results from web service.");
lblStatus.Text = "";
}
/** Re-enable the button */
btnAction.IsEnabled = true;
});
});
}
Now, I would like to:
Write the same code in a way that uses a callback instead of using Dispatcher.Invoke().
Be able to cancel a running task that calls doSomething()
Be able to chain multiple calls, i.e. await doSomething() and after it finished, doAnotherThing() with the results from the previous call
Hence why I want to write this using the async model.
What do I do?

You would mark your method as async, await the Task.Run so the continuations run on the UI, also leaving only the long running (seemingly CPU bound) job within it
private async void btnAction_Click(object sender, RoutedEventArgs e)
{
btnAction.IsEnabled = false;
txtResult.Text = "";
lblStatus.Text = "Wait...";
string input = query.Text;
// calling a method that takes a long time (>3s) to finish and return
var attempt = await Task.Run(() => someLibrary.doSomethingWith(input));
if (attempt.ContainsKey("success"))
{
if (attempt["success"] == true)
{
txtResult.Text = "Success! The door is: " + (attempt["is_open"] ? "open" : "closed");
lblStatus.Text = "";
}
else
{
lblStatus.Text = "Error! The service says: " + attempt["errorMessage"];
}
}
else
{
MessageBox.Show("There was a problem getting results from web service.");
lblStatus.Text = "";
}
btnAction.IsEnabled = true;
}
Update
To cancel the task, you would use a CancellationToken from am instance of CancellationTokenSource and pass that into Task.Run and also your long running method to check for IsCancellationRequested (if you can). You cancel by calling CancellationTokenSource.Cancel
Note you would likely want to wrap this in a try catch finally and catch on OperationCanceledException and place your button enabling code in the finally

The async modifier requires that the function return Task<T> (or void, in which case any await statements will be ignored). This means that using async and using Task.Run() are one and the same, the premise of your question doesn't make sense.
However, what I think you want to do is just use the async await syntax to avoid an explicit call to Task.Run().
Callbacks
Just create a function which returns a Task
async Task<T> Foo()
And then assign a variable var bar=await Foo();
Cancel a running task
Just use CancellationToken
CancellationTokenSource tokenSource = new CancellationTokenSource();
If you construct a task with two arguments, the second argument is a cancellation token:
var bar= new Task(action, tokenSource.Token)
This lets you use
tokenSource.Cancel();
Relevant link: https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken?view=netframework-4.8
Chaining Calls
If you don't require a defined order of execution, you can use Task.WhenAll(), otherwise you can either execute the next task inside the previous one or in the awaiting code.
Task.WhenAll(): https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.whenall?view=netframework-4.8#System_Threading_Tasks_Task_WhenAll_System_Collections_Generic_IEnumerable_System_Threading_Tasks_Task__

Related

Wrapping up an async method for a non-async caller

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();
}

C# How to handle cancel task with eventhandler inside

I have requirement to update ui control when status of dependent service will change. I have this sample code, which polling service api to get status and sends result to recalculate and update ui by main thread:
public void StartObserving() {
this.cts = new CancellationTokenSource();
this.cts.Token.ThrowIfCancellationRequested();
this.isRunning = true;
var token = this.cts.Token;
Task.Run(async () =>
{
try
{
while (this.isRunning)
{
var result = this.serviceAPI.GetStatus();
this.OnServiceStatusChanged(result);
await Task.Delay(3000);
}
}
catch (OperationCanceledException)
{
this.isRunning = false;
}
catch (Exception ex)
{
this.isRunning = false;
this.logger.LogError(ex);
}
}, token);
}
And the problem is when I want to cancel above Task. When I call this.cts.Cancel() in another method in this class, I get Exception 'A task was canceled' on dispatcher which was triggered by EventHandler: OnServiceStatusChanged
How I should properly implement this scenario?
I would simply check whether the token in cancelled in the inner loop, and exit the loop if it is. No need to pass the token to the Task.Run() method.
public void StartObserving()
{
this.cts = new CancellationTokenSource();
var token = this.cts.Token;
Task.Run(async () =>
{
try
{
while (!token.IsCancellationRequested)
{
var result = this.serviceAPI.GetStatus();
this.OnServiceStatusChanged(result);
await Task.Delay(3000);
}
}
catch
{
}
});
}
Tried to simulate this behavior in a console app. Task started, but after calling cts.Cancel(), the task continues to execute... Very strange.
However, I could cancel the task by simply setting this.isRunning to false (instead of calling cts.Cancel()). But I am not sure if this is the solution you want.
If serviceAPI.GetStatus() is a blocking call that waits indeffinitly, then you cannot properly cancel this task.
Proper cancellation of async methods involves marking safe cancellation points with CancellationToken.ThrowIfCancellationRequested().
You would have to rewrite serviceAPI.GetStatus() as an async method that you await the result of. It should contain calls to CancellationToken.ThrowIfCancellationRequested() at points where it can be safely cancelled. You would want to pass the cancellation token in to both that method, and the call to Task.Delay() for optimal performance.

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 to handle task cancellation in the TPL

Good day! I am writing a helper library for WinForms UI. Started using TPL async/await mechanism and got a problem with this kind of code example :
private SynchronizationContext _context;
public void UpdateUI(Action action)
{
_context.Post(delegate { action(); }, null);
}
private async void button2_Click(object sender, EventArgs e)
{
var taskAwait = 4000;
var progressRefresh = 200;
var cancellationSource = new System.Threading.CancellationTokenSource();
await Task.Run(() => { UpdateUI(() => { button2.Text = "Processing..."; }); });
Action usefulWork = () =>
{
try
{
Thread.Sleep(taskAwait);
cancellationSource.Cancel();
}
catch { }
};
Action progressUpdate = () =>
{
int i = 0;
while (i < 10)
{
UpdateUI(() => { button2.Text = "Processing " + i.ToString(); });
Thread.Sleep(progressRefresh);
i++;
}
cancellationSource.Cancel();
};
var usefulWorkTask = new Task(usefulWork, cancellationSource.Token);
var progressUpdateTask = new Task(progressUpdate, cancellationSource.Token);
try
{
cancellationSource.Token.ThrowIfCancellationRequested();
Task tWork = Task.Factory.StartNew(usefulWork, cancellationSource.Token);
Task tProgress = Task.Factory.StartNew(progressUpdate, cancellationSource.Token);
await Task.Run(() =>
{
try
{
var res = Task.WaitAny(new[] { tWork, tProgress }, cancellationSource.Token);
}
catch { }
}).ConfigureAwait(false);
}
catch (Exception ex)
{
}
await Task.Run(() => { UpdateUI(() => { button2.Text = "button2"; }); });
}
Basically, the idea is to run two parallel tasks - one is for, say, progress bar or whatever update and a sort of timeout controller, the other is the long running task itself. Whichever task finishes first cancels the other one. So, there should not be a problem to cancel the "progress" task as it has a loop in which I can check if task is marked cancelled. The problem is with the long running one. It could be Thread.Sleep() or SqlConnection.Open(). When I run CancellationSource.Cancel(), the long running task keeps working and does not cancel. After a timeout I am not interested in long running task or whatever it may result in.
As the cluttered code example may suggest, I have tried a bunch of variants and none given me a desired effect. Something like Task.WaitAny() freezes UI... Is there a way to make that cancellation work or may be even a different approach to code these things?
UPD:
public static class Taskhelpers
{
public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
{
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
}
return await task;
}
public static async Task WithCancellation(this Task task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
{
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
}
await task;
}
}
.....
var taskAwait = 4000;
var progressRefresh = 200;
var cancellationSource = new System.Threading.CancellationTokenSource();
var cancellationToken = cancellationSource.Token;
var usefulWorkTask = Task.Run(async () =>
{
try
{
System.Diagnostics.Trace.WriteLine("WORK : started");
await Task.Delay(taskAwait).WithCancellation(cancellationToken);
System.Diagnostics.Trace.WriteLine("WORK : finished");
}
catch (OperationCanceledException) { } // just drop out if got cancelled
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine("WORK : unexpected error : " + ex.Message);
}
}, cancellationToken);
var progressUpdatetask = Task.Run(async () =>
{
for (var i = 0; i < 25; i++)
{
if (!cancellationToken.IsCancellationRequested)
{
System.Diagnostics.Trace.WriteLine("==== : " + i.ToString());
await Task.Delay(progressRefresh);
}
}
},cancellationToken);
await Task.WhenAny(usefulWorkTask, progressUpdatetask);
cancellationSource.Cancel();
By modifying for (var i = 0; i < 25; i++) limit of i I imitate whether long running task finishes before the progress task or otherwise. Works as desired. The WithCancellation helper method does the job, although two sort of 'nested' Task.WhenAny look suspicious for now.
I agree with all the points in Paulo's answer - namely, use modern solutions (Task.Run instead of Task.Factory.StartNew, Progress<T> for progress updates instead of manually posting to the SynchronizationContext, Task.WhenAny instead of Task.WaitAny for asynchronous code).
But to answer the actual question:
When I run CancellationSource.Cancel(), the long running task keeps working and does not cancel. After a timeout I am not interested in long running task or whatever it may result in.
There are two parts to this:
How do I write code that responds to a cancellation request?
How do I write code that ignores any responses after the cancellation?
Note that the first part deals with cancelling the operation, and the second part is actually dealing with cancelling the waiting for the operation to complete.
First things first: support cancellation in the operation itself. For CPU-bound code (i.e., running a loop), periodically call token.ThrowIfCancellationRequested(). For I/O-bound code, the best option is to pass the token down to the next API layer - most (but not all) I/O APIs can (should) take cancellation tokens. If this isn't an option, then you can either choose to ignore the cancellation, or you can register a cancellation callback with token.Register. Sometimes there's a separate cancellation method you can call from your Register callback, and sometimes you can make it work by disposing the object from the callback (this approach often works because of a long-standing Win32 API tradition of cancelling all I/O for a handle when that handle is closed). I'm not sure if this will work for SqlConnection.Open, though.
Next, cancelling the wait. This one is relatively simple if you just want to cancel the wait due to a timeout:
await Task.WhenAny(tWork, tProgress, Task.Delay(5000));
When you write something like await Task.Run(() => { UpdateUI(() => { button2.Text = "Processing..."; }); }); on your button2_Click, you are, from the UI thread, scheduling an action to a thread poll thread that posts an action to the UI thread. If you called the action directly, it would be quickier because it wouldn't have two context switchings.
ConfigureAwait(false) causes the synchronization context to not being captured. I should not be used inside UI methods because, you most certainely, want to do some UI work on the continuation.
You shouldn't use Task.Factory.StartNew instead of Task.Run unless you absolutely have a reason to. See this and this.
For progress updates, consider using the Progress<T> class, because it captures the synchronization context.
Maybe you should try something like this:
private async void button2_Click(object sender, EventArgs e)
{
var taskAwait = 4000;
var cancellationSource = new CancellationTokenSource();
var cancellationToken = cancellationSource.Token;
button2.Text = "Processing...";
var usefullWorkTask = Task.Run(async () =>
{
try
{
await Task.Dealy(taskAwait);
}
catch { }
},
cancellationToken);
var progress = new Progress<imt>(i => {
button2.Text = "Processing " + i.ToString();
});
var progressUpdateTask = Task.Run(async () =>
{
for(var i = 0; i < 10; i++)
{
progress.Report(i);
}
},
cancellationToken);
await Task.WhenAny(usefullWorkTask, progressUpdateTask);
cancellationSource.Cancel();
}
I think you need to check IsCancellationRequested in the progressUpdate Action.
As to how to do what you want, this blog discusses an Extension method WithCancellation that will make it so that you stop waiting for your long running task.

Using async/await with Live SDK

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;
}
}

Categories