How do you cancel an async operation in C++/CX from C# - c#

I'm trying to cancel an operation that is written in C++/CX from C#. Despite the fact I wrote both pieces of code I can't get the operation to cancel properly when awaiting it from the C# side. Here's an example:
From C#:
var tcs = new CancellationTokenSource();
tcs.Cancel();
var class1 = new MyClass();
try
{
var asyncOp = await class1.DoSomeTaskAsync().AsTask(tcs.Token);
}
catch (OperationCanceledException oce)
{
//I want to get here
Handle(oce);
}
From C++:
IAsyncOperation<bool>^ MyClass::DoSomeTaskAsync(){
return concurrency::create_async([](concurrency::cancellation_token ct) {
task<bool> my_task([]() {
doSomething1();
if (concurrency::is_task_cancellation_requested())
{
concurrency::cancel_current_task();
}
doSomething2();
return false;
}, ct);
return my_task;
});
}
The problem seems to be that passing in the token into the AsTask extension method does nothing when calling a task across the ABI. When debugging the C++ side both the ct and the is_task_cancellation_requested() function indicate that a cancellation has not been requested.

Try this. In your C++ Windows Runtime Component do something that takes a long time, for example, concurrency::wait(2000) will sleep the thread for two seconds (do not do this in a real app).
#include <ppltasks.h>
using namespace Windows::Foundation;
IAsyncOperation<bool>^ MyClass::DoSomeTaskAsync()
{
return concurrency::create_async([=](concurrency::cancellation_token token)
{
// Do something.
concurrency::wait(2000);
if (concurrency::is_task_cancellation_requested())
{
concurrency::cancel_current_task();
}
// Do something else.
concurrency::wait(2000);
return true;
});
}
Then, in your C# Windows Store App create two buttons:
<Button x:Name="DoButton" Click="DoButton_Click">Do</Button>
<Button x:Name="CancelButton" Click="CancelButton_Click">Cancel</Button>
Call your component's asynchronous method in the Do button.
private System.Threading.CancellationTokenSource cts;
private async void DoButton_Click(object sender, RoutedEventArgs e)
{
cts = new System.Threading.CancellationTokenSource();
var class1 = new MyClass();
try
{
var asyncOp = await class1.DoSomeTaskAsync().AsTask(cts.Token);
System.Diagnostics.Debug.WriteLine(asyncOp);
}
catch (OperationCanceledException oce)
{
// I want to get here.
System.Diagnostics.Debug.WriteLine(oce);
}
}
And cancel the operation in the Cancel button.
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
cts.Cancel();
cts = null;
}
Run the app, click Do button and within two seconds click Cancel button. You should get a System.Threading.OperationCanceledException.

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

Using async instead of Task.Run()

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__

How to Call an Async Method from within ElapsedEventHandler

I'm going to use a Windows Service to send Telegram Messages periodically (Every two minutes). My Windows Service starts fine and after 2 minutes it is stopped. I checked my code and find out it is because of async. How can I solve the problem?
protected override void OnStart(string[] args)
{
//< I declared a System.Timers.Timer to send new Telegram messages.
aTimer = new System.Timers.Timer(120000); // 2 minutes
aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
aTimer.Enabled = true;
GC.KeepAlive(aTimer);
//>
}
private static void OnTimedEvent(object source, ElapsedEventArgs e)
{
SendNewMessages();
}
async static void SendNewMessages()
{
MyDataContext myDB = new MyDataContext();
var newMessages = myDB.TelegramMessages.Where(tm => tm.Status != "New Message");
foreach (TelegramMessage newMessage in newMessages)
{
try
{
var store = new FileSessionStore();
var client = new TelegramClient(store, "MySession");
await client.Connect();
var res = await client.ImportContactByPhoneNumber(newMessage.ReceiverPhoneNumber);
await client.SendMessage(res.Value, newMessage.Message);
newMessage.Status = "Sent";
myDB.SubmitChanges();
}
catch (Exception ex)
{
newMessage.Status = ex.Message;
myDB.SubmitChanges();
}
Thread.Sleep(5000);
}
}
One thing I see directly is that async/await has not been implemented all the way to the eventhandler since "SendNewMessages" returns void. And your eventhandler is not async.
According to MSDN on "Async Return Types (C# and Visual Basic)"
The primary use of the void return type (Sub procedures in Visual Basic) is in event handlers, where a void return type is required. A void return also can be used to override void-returning methods or for methods that perform activities that can be categorized as "fire and forget."
This is most likely an issue in your scenario, so you could try changing your SendNewMessage to this
async static Task SendNewMessages()
And your eventhandler to this
private async static void OnTimedEvent(object source, ElapsedEventArgs e)
{
await SendNewMessages();
}
UPDATED
It would also be a good idea to add some errorhandling code to your "SendNewMessages"-method since if an exception is thrown, your service will quit.
async static Task SendNewMessages()
{
try
{
... Your code here
}
catch(Exception e)
{
... exceptionhandling here
}
}
At the moment you only have exceptionhandling within your foreach, but you do not have any errorhandling (as far as I can see) for your database-code.
if an exception is throw here
MyDataContext myDB = new MyDataContext();
var newMessages = myDB.TelegramMessages.Where(tm => tm.Status != "New Message");
foreach (TelegramMessage newMessage in newMessages)
or here:
newMessage.Status = ex.Message;
myDB.SubmitChanges();
The service would end

WP8.1 - CancellationToken and async still await forever and never end

I have a really strange problem and I don't know how to solve it.
I have these two methods in different classes.
The first one is triggered when a button in the CommandBar is pressed.
EDIT: I created two similar but smaller methods to show you the problem:
private async void runCode(object sender, RoutedEventArgs e)
{
BottomAppBar.IsEnabled = false;
object result = await endlessLoopTest();
BottomAppBar.IsEnabled = true;
}
private async Task<object> endlessLoopTest()
{
var tokenSource = new System.Threading.CancellationTokenSource(500);
try
{
await Task.Run(() =>
{
while (true)
{
//Infinite loop to test the code
}
}, tokenSource.Token);
return null;
}
catch (OperationCanceledException)
{
return new TextBlock();
}
}
I added a cancellationToken that expires after 1500ms (I assume that if the interpreter takes longer to process the code, it has been trapped in a loop).
The first time I try this it usually works, but if I try again, the CommandBar buttons never get enabled again, so I assume that task is being awaited forever, and I don't know why, as I added that cancellationToken.
Do you know what could be wrong here?
Thanks for your help!
Sergio
You are about 2/3's of the way there. When using a CancellationToken + CancellationTokenSournce, one must ask the token if it was cancelled. There are a number of ways to subscribe to that, including calling the token's ThrowIfCancelledRequest method or checking the token's Boolean property IsCancellationRequested and breaking out of the loop. See Cancellation in Managed Threads.
Here's a small example that can run in a Console app. Note, in UI based apps, use await, not Task.Wait().
private static void CancelTask()
{
CancellationTokenSource cts = new CancellationTokenSource(750);
Task.Run(() =>
{
int count = 0;
while (true)
{
Thread.Sleep(250);
Console.WriteLine(count++);
if (cts.Token.IsCancellationRequested)
{
break;
}
}
}, cts.Token).Wait();
}
The result is 0 1 2 and then the Task and program exit.

Multi-Threading Cross-Class Cancellation with TPL

All, I have a long running process that I run on a background thread (with cancellation support) using the Task Paralell Library (TPL). The code for this long running taks is contained within Class Validation, and when the method
public bool AsyncRunValidationProcess(TaskScheduler _uiScheduler,
CancellationToken _token, dynamic _dynamic = null)
{
try
{
// Note: _uiScheduler is used to update the UI thread with progress infor etc.
for (int i = 0; i < someLargeLoopNumber; i++)
{
// Cancellation requested from UI Thread.
if (_token.IsCancellationRequested)
_token.ThrowIfCancellationRequested();
}
return true;
}
catch (Exception eX)
{
// Do stuff. Display `eX` if cancellation requested.
return false;
}
}
is run from Class Validation I can cancel the process fine. The cancellation request is handled by the appropriate delegate (shown below) and this works fine (I don't belive this is the cause of my problem).
When I run this method from another class, Class Batch, I do this via a "controller" method
asyncTask = Task.Factory.StartNew<bool>(() => asyncControlMethod(), token);
which in turn invokes the method
valForm.AsyncRunValidationProcess(uiScheduler, token,
new List<string>() { strCurrentSiteRelPath }));
where valForm is my accessor to Class Validation, the method runs fine, but when I attempt a cancellation the delegate
cancelHandler = delegate
{
UtilsTPL.CancelRunningProcess(asyncTask, cancelSource);
};
where
public static void CancelRunningProcess(Task _task,
CancellationTokenSource _cancelSource)
{
try
{
_cancelSource.Cancel();
_task.Wait(); // On cross-class call it freezes here.
}
catch (AggregateException aggEx)
{
if (aggEx.InnerException is OperationCanceledException)
Utils.InfoMsg("Operation cancelled at users request.");
if (aggEx.InnerException is SqlException)
Utils.ErrMsg(aggEx.Message);
}
}
freezes/hangs (with no unhandled exception etc.) on _task.Wait(). This (I belive - through testing) is to do with the fact that I am cancelling asyncControlMethod() which has called valForm.AsyncRunValidationProcess(...), so it is cancelling asyncControlMethod() which is causing the current process to hang. The problem seems to be with passing the CancellationTokenSource etc. to the child method. The IsCancellationPending event fires and kills the controlling method, which causes the child method to hang.
Can anyone tell me what I am doing wrong or (more pertinently), what should I be doing to allow such a cancellation procedure?
Note: I have tried to spawn a child task to run valForm.AsyncRunValidationProcess(...), with its own CancellationToken but this has not worked.
Thanks for your time.
The answer to this problem (helped massively by Jiaji Wu's comment and link) was that you cannot declare the CancellationToken as a global variable that is passed to the cascading methods; that is, you cannot have
public class MainClass
{
private CancellationTokenSource = source;
private CancellationToken token;
public MainClass()
{
source = new CancellationtokenSource();
token = source.Token;
}
private void buttonProcessSel_Click(object sender, EventArgs e)
{
// Spin-off MyMethod on background thread.
Task<bool> asyncControllerTask = null;
TaskSpin(asyncControllerTask, cancelSource, token, MyMethod);
}
private void method()
{
// Use the global token DOES NOT work!
if (token.IsCancellationRequested)
token.ThrowIfCancellationRequested();
}
private void TaskSpin(Task<bool> asyncTask, CancellationTokenSource cancelSource,
CancellationToken token, Func<bool> asyncMethod)
{
try
{
token = cancelSource.Token;
asyncTask = Task.Factory.StartNew<bool>(() => asyncMethod(token), token);
// To facilitate multitasking the cancelTask ToolStripButton
EventHandler cancelHandler = null;
if (cancelSource != null)
{
cancelHandler = delegate
{
UtilsTPL.CancelRunningProcess(mainForm, uiScheduler, asyncTask, cancelSource, true);
};
}
// Callback for finish/cancellation.
asyncTask.ContinueWith(task =>
{
// Handle cancellation etc.
}
// Other stuff...
}
}
}
Use of the global token in the maethod run on the background thread doen NOT work! The method must be explicitly passed the token for it to be able to register it. I am not sure of the exact reason why this is the case, but I will know in future, now you need to pass the token to MyMethod() like this
private void method(CancellationToken token)
{
// Use the global token DOES NOT work!
if (token.IsCancellationRequested)
token.ThrowIfCancellationRequested();
}
I hope this helps someone else.

Categories