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.
Related
I'm trying to chain tasks, so as soon as the one finishes the next starts, but the UI doesn't update. I did a course in react and the one lesson is where you update the UI based on state changes in the application, and that is what I'm trying to replicate. Change the state of the application (basically I'll be running methods that run return a bool for validation), and then update the UI accordingly, I'm also using binding, but for some reason its not running as intended, I don't know if I follow the documentation incorrectly. What can I change or fix to make this work and is it practically correct to use more than one task in a single async Task<T> method
public async Task<string> Connect_To_Ip()
{
await Task.Run(() =>
{
details.State = "Connection To IP 127.0.01.258.....";
Task.Delay(5000).Wait();
}).ContinueWith(result => new Task(async () =>
{
await Task.Run(() =>
{
if (result.Status == TaskStatus.RanToCompletion)
{
details.State = "Validating Card Number......";
}
});
}), TaskContinuationOptions.OnlyOnRanToCompletion);
return details.State;
}
How I'm calling the original task
Task connect = Connect_To_Ip();
await connect;
When you use await then you don't need Task.ContinueWith. Everything that follows the awaited operation is a continuation. Since you want to validate on a background thread, you must post the changes back to the UI thread in order to update the UI elements, otherwise you will produce cross-thread exceptions.
This is because UI elements can't be updated from a background thread, except the update occurs via INotifyPropertyChanged and data binding.
One way to do this is to use the Dispatcher to invoke UI manipulations on the UI thread or use the Progress<T> class, which will always execute the registered callback on the UI thread.
Your fixed and simplified code could look like this example:
public async Task ValidateAsync()
{
// Register the callback that updates the UI with the 'progressReporter'.
// Progress<T> must be instantiated on the UI thread it is associated with
var progressReporter = new Progress<string>(message => details.State = message);
// Execute the operation on a background thread
await Task.Run(() => ConnectToIp(progressReporter));
// Continuation starts here, after await
}
public async Task ConnectToIp(IProgress<string> progressReporter)
{
progressReporter.Report("Connection To IP 127.0.01.258.....");
await Task.Delay(TimeSpan.FromSeconds(5));
// Continuation starts here, after await
progressReporter.Report("Validating Card Number......");
}
It is recommended to use async APIs when possible instead of using background threads. For example, to connect to a server without blocking the UI you can use
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync("http://www.contoso.com/");
Many IO classes provide an async API.
Furthermore, I recommend to take a look at the INotifyDataErrorInfo interface. It is the recommended way to implement property validation and allows to provide UI error feedback in a very easy way.
I did this in Windows Forms (I had a test Windows Forms project open), but it should be about the same in WPF. I dropped a button, a label and a text box on the form. Then I wrote this code:
private async void button1_Click(object sender, EventArgs e)
{
var result = await ValidateTextBox();
if (result != null)
{
label1.Text = result;
return;
}
var intResult = await ReadTextBox();
label1.Text = intResult.ToString();
await IncrementTextBox();
intResult = await ReadTextBox();
label1.Text = intResult.ToString();
}
private async Task<string> ValidateTextBox()
{
await Task.Delay(2000);
if (!int.TryParse(textBox1.Text, out _)) {
return "Not Valid";
}
//otherwise
return null;
}
private async Task<int> ReadTextBox()
{
await Task.Delay(3000);
if (!int.TryParse(textBox1.Text, out var result))
{
throw new Exception("Don't do that");
}
return result;
}
private async Task IncrementTextBox()
{
await Task.Delay(3000);
if (!int.TryParse(textBox1.Text, out var result))
{
throw new Exception("Don't do that");
}
textBox1.Text = (result + 1).ToString();
}
If you type something that's not an int into the text box and press the button, a few seconds go by, and then Not Valid shows up in the label.
If there is a number there, then there is a pause and the number shows up in the label. Then another pause and the text box number will increment by 1. Finally after another pause, the label will show the incremented value.
Note that this all runs on a single thread. But, in spite of all the delays, the UI remains responsive the whole time.
Put breakpoints at the start of each function and on the lines after each of the awaits in the button click handler. Step through (not into) the whole thing and you'll see how the awaits create continuations
I'm trying to implement a responsive search text box, where the search results are updated as the user types in the search text. I'm trying to do this using Tasks, but I'm finding that sometimes a previous search finishes after a later search, thereby replacing the new results with old results.
One quick fix is to delay starting the search after the user has stopped typing for half a second or so. However, I think this only hides the problem. I'd like to find a general solution.
So far, I've come up with the following class:
public class LastAddedTaskStrategy<T>
{
private DateTime latest;
public T Result { get; private set; }
public async Task Add(Task<T> t)
{
var timestamp = DateTime.Now;
latest = timestamp;
var currentResult = await t;
if (timestamp >= latest)
Result = currentResult;
}
}
The idea is that you can add (and await) as many Tasks as you want (e.g., searches), but only the results of the last added (not last completed) Task wins. So if a previous search finishes after a later search, the Results property won't be updated because it's an old result.
Is this a good solution or is there something better?
Here is an extension method for System.Windows.Forms controls, that subscribes to their TextChanged event. Every time the event is fired an asynchronous method is invoked, and the result of this method is propagated to a handler. The result is propagated only under the condition that the asynchronous method will not be preempted before its completion.
public static void OnTextChangedExecute<TResult>(this Control control,
Func<CancellationToken, Task<TResult>> function,
Action<TResult> handler)
{
CancellationTokenSource activeCTS = null;
control.TextChanged += Event_Handler;
async void Event_Handler(object sender, EventArgs args)
{
activeCTS?.Cancel();
TResult result;
using (var cts = new CancellationTokenSource())
{
activeCTS = cts;
try
{
result = await function(cts.Token);
cts.Token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException)
when (cts.Token.IsCancellationRequested)
{
return; // Preempted, don't invoke the handler.
}
finally
{
if (activeCTS == cts) activeCTS = null;
}
}
handler(result);
}
}
Usage example:
public Form1()
{
InitializeComponent();
TextBox1.OnTextChangedExecute(
ct => SearchAsync(TextBox1.Text, ct), TextBox1_SearchCompleted);
}
async Task<int> SearchAsync(string text, CancellationToken token)
{
await Task.Delay(1000, token); // Simulate some cancelable I/O operation
return Int32.Parse(text);
}
void TextBox1_SearchCompleted(int result)
{
MessageBox.Show($"Result: {result}");
}
The OnTextChangedExecute method is not thread-safe. It is intended to be called from the UI thread only. It depends on the existence
of a WindowsFormsSynchronizationContext to work properly.
I was recently exposed to C# language and was working on getting data out of cassandra so I was working with below code which gets data from Cassandra and it works fine.
Only problem I have is in my ProcessCassQuery method - I am passing CancellationToken.None to my requestExecuter Function which might not be the right thing to do. What should be the right way to handle that case and what should I do to handle it correctly?
/**
*
* Below method does multiple async calls on each table for their corresponding id's by limiting it down using Semaphore.
*
*/
private async Task<List<T>> ProcessCassQueries<T>(IList<int> ids, Func<CancellationToken, int, Task<T>> mapperFunc, string msg) where T : class
{
var tasks = ids.Select(async id =>
{
await semaphore.WaitAsync();
try
{
ProcessCassQuery(ct => mapperFunc(ct, id), msg);
}
finally
{
semaphore.Release();
}
});
return (await Task.WhenAll(tasks)).Where(e => e != null).ToList();
}
// this might not be good idea to do it. how can I improve below method?
private Task<T> ProcessCassQuery<T>(Func<CancellationToken, Task<T>> requestExecuter, string msg) where T : class
{
return requestExecuter(CancellationToken.None);
}
As said in the official documentation, the cancellation token allows propagating a cancellation signal. This can be useful for example, to cancel long-running operations that for some reason do not make sense anymore or that are simply taking too long.
The CancelationTokenSource will allow you to get a custom token that you can pass to the requestExecutor. It will also provide the means for cancelling a running Task.
private CancellationTokenSource cts = new CancellationTokenSource();
// ...
private Task<T> ProcessCassQuery<T>(Func<CancellationToken, Task<T>> requestExecuter, string msg) where T : class
{
return requestExecuter(cts.Token);
}
Example
Let's take a look at a different minimal/dummy example so we can look at the inside of it.
Consider the following method, GetSomethingAsync that will yield return an incrementing integer every second.
The call to token.ThrowIfCancellationRequested will make sure a TaskCanceledException is thrown if this process is cancelled by an outside action. Other approaches can be taken, for example, check if token.IsCancellationRequested is true and do something about it.
private static async IAsyncEnumerable<int> GetSomethingAsync(CancellationToken token)
{
Console.WriteLine("starting to get something");
token.ThrowIfCancellationRequested();
for (var i = 0; i < 100; i++)
{
await Task.Delay(1000, token);
yield return i;
}
Console.WriteLine("finished getting something");
}
Now let's build the main method to call the above method.
public static async Task Main()
{
var cts = new CancellationTokenSource();
// cancel it after 3 seconds, just for demo purposes
cts.CancelAfter(3000);
// or: Task.Delay(3000).ContinueWith(_ => { cts.Cancel(); });
await foreach (var i in GetSomethingAsync(cts.Token))
{
Console.WriteLine(i);
}
}
If we run this, we will get an output that should look like:
starting to get something
0
1
Unhandled exception. System.Threading.Tasks.TaskCanceledException: A task was canceled.
Of course, this is just a dummy example, the cancellation could be triggered by a user action, or some event that happens, it does not have to be a timer.
I know this is probably one I should be able to google easily enough, but just not sure what to search for at the moment...
Basically, I have an event that is raised and while it is still executing it may be raised again. If this happens, I would like to immediately terminate any previous execution.
In this specific case, I'm talking about a SelectionChanged event for a DataGridView. Before I introduced async programming to this project there was no issue. But now I'm finding that as the method waits for results from an async call, it may be raised again. And the new call may finish before the original call which results in an inconsistent UI state.
How about something like this:
private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
public async void OnMainThread()
{
cancellationTokenSource.Cancel();
var newCts = new CancellationTokenSource();
cancellationTokenSource = newCts;
try
{
var result = await AsyncOp(newCts.Token);
if (!newCts.IsCancellationRequested)
{
// Update UI
}
}
catch (OperationCanceledException)
{
// Ignore
}
}
public async Task<int> AsyncOp(CancellationToken cancel)
{
// Do the async work
return 0;
}
I have a search method that returns search suggestions to the UI. This method is fired every time the user enters a new character into the search box.
I have added some cancellation code to cancel the previous search request. This works but not all the time.
private CancellationTokenSource cancellationTokenSource;
private async Task UserSearch(string searchCriteria)
{
Debug.WriteLine("Searching for {0}....", searchCriteria);
try
{
var cts = new CancellationTokenSource();
this.Suggestions = await this.SearchAsync(searchCriteria, cts.Token);
}
catch (OperationCanceledException)
{
Debug.WriteLine("Search({0}) cancelled", searchCriteria);
}
}
private async Task<IList<string>> SearchAsync(string searchCriteria, CancellationToken cancelToken)
{
CancellationTokenSource previousCts = this.cancellationTokenSource;
CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancelToken);
this.cancellationTokenSource = linkedCts;
// if previous task running cancel it
if (previousCts != null)
{
previousCts.Cancel();
}
linkedCts.Token.ThrowIfCancellationRequested();
List<string> results =
(await this.searchProvider.SearchAsync(searchCriteria, linkedCts.Token)).ToList();
Debug.WriteLine("Search({0}) returned {1} records", searchCriteria, results.Count);
linkedCts.Dispose();
this.cancellationTokenSource = null;
return results;
}
For example. I get the following debug messages:
SearchTerm changing to: Di
Searching for Di....
SearchTerm changing to: Dia
Searching for Dia....
Search(Di) cancelled
SearchTerm changing to: Diap
Searching for Diap....
Search(Diap) returned 323 records
Search(Dia) returned 3230 records
As you can see the first search gets cancelled but the second one doesn't and it gets returned after the last search giving incorrect results to the user.
How can I ensure previous tasks are always cancelled?
I think you are probably complicating your solution a little. All you need to do is see if there is an existing operation in progress that hasn't been cancelled and cancel it. Then perform the new search. Untested, but I think this should do it.
private CancellationTokenSource cancellationTokenSource;
private async Task UserSearch(string searchCriteria)
{
Debug.WriteLine("Searching for {0}....", searchCriteria);
try
{
if(cancellationTokenSource != null &&
!cancellationTokenSource.IsCancellationRequested)
{
cancellationTokenSource.Cancel();
}
cancellationTokenSource = new CancellationTokenSource();
this.Suggestions = await this.searchProvider.SearchAsync(searchCriteria, linkedCts.Token);
Debug.WriteLine("Search({0}) returned {1} records", searchCriteria, results.Count);
}
catch (OperationCanceledException)
{
Debug.WriteLine("Search({0}) cancelled", searchCriteria);
}
}
You cancel your previous query too late, query it as soon as you are able (directly after receiving user input, not inside the next query).
I suppose the problem is multithread access to this.cancellationTokenSource. If say thread 2 cancels thread 1 too late (searchProvider.SearchAsync is completed) this.cancellationTokenSource can be cleared after assignment (i.e. thread 2 executes this.cancellationTokenSource = linkedCts; and thread 1 after that calls this.cancellationTokenSource = null;). This will effectively disable cancelling of thread 2 completely
Thus you'd better have one search cancelled before the next one starts like in the suggestion from #Ned Stoyanov