Thread cancellation token for direct search bar - c#

I have a datagridview filtered by a search bar. I wanted to reproduce the google like search on keyup.
Since the database might get bigger, I was trying to cancel previous search on next charachter input (it is currently quite fast, so I setup a sleep).
It seems that the canceltoken is not checked everytime between the cancelation order and the new creation. (line 2 and line 5) which seems normal, but anoying for the purpose.
Is there a "Show this to all thread before setting a new one" methods for the said token? Or a way to call an old token? Maybe a list? Set a dictionnary with a datetime?
Any advices on this kind of system would be very welcome.
private CancellationTokenSource cts { get; set; }
protected async void SearchGrid(object Sender, EventArgs e)
{
FullGridView.CurrentCell = null;
cts = cts ?? new CancellationTokenSource
();
cts.Cancel();
List<string> SearchFor = Box.Text.Split(null).ToList();
cts = new CancellationTokenSource();await Task.Run(() =>
{
try
{
foreach (DataGridViewRow Row in FullGridView.Rows)
{ if ((Row.Cells[0].Value as bool?) == true)
{ continue; }
cts.Token.ThrowIfCancellationRequested();
bool Found = false;
Found = SearchFor.All(s =>
ColumnIndexToSearch.Any(c =>
Row.Cells[c].Value != null &&
Row.Cells[c].Value.ToString().ToUpperInvariant()
.Contains(s.ToUpperInvariant())));
SyncCtx.Post(delegate
{
Row.Visible = Found;
}, null);
Thread.Sleep(5000); //Test purpose
}
}
catch
{
return;
}
}, cts.Token);

Finally I created a List<CancellationTokenSource> cts then I cancel Last() then create a new one. It keeps alive the token and avoid the race condition.

Related

Canceling search using a cancellationToken

my challenge is a rather common one, I have a heavily populated treeview that i want to filter. To do this i want to have a text box that the user enters in their filter text and after the treeview is filtered to show nodes with that particular filter text in their header.
so what i've opted to do is to have a textbox that then has a text change event that has a delay before it starts it filtering process, now obviously if the filter text changes before the delay finishes i want to cancel the process and start a new one with the new fitler text.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace pav.skillsToCompetenciesMapper.Views
{
public partial class MapSkillsPage : Page
{
CancellationTokenSource cts;
private async void Search_TEXTBOX_TextChanged(object sender, TextChangedEventArgs e)
{
if (cts != null) cts.Cancel();
var searchText = Search_TEXTBOX.Text;
try
{
using (cts = cts ?? new CancellationTokenSource())
await Task.Delay(3000, cts.Token).ContinueWith(tr =>
{
var st = searchText;
//Do search here
}, TaskContinuationOptions.NotOnCanceled);
}
catch (OperationCanceledException) { }
finally { cts = null; }
}
}
}
Now the above seems to work for me, I'm just worried that this try catch solution is a bit clunky, it really seems as if i should be able to use the TaskContinuation.OnlyOnCanceled to avoid using a try catch for logic. just seems like a code smell to me, but that's a side note.
My real problem occurs when i try to actually search the Treeview like so where the above "Do Search Here" comment is
foreach (TreeViewItem category in Abilities_TreeView.Items)
foreach (DragableTreeViewItem ability in category.Items)
if (!ability.Header.ToString().Contains(filterText))
ability.Visibility = Visibility.Hidden;
Any help on this would be greatly appreciated, my suspicion is that it has something to do with trying to access the UI thread from a background thread, but i'm not 100% sure if i'm barking up the right tree.
If you don't want to handle the OperationCanceledException, you could use the overload of the ContinueWith method that only accepts a continuation action and check the value of the IsCanceled property inside this action:
try
{
using (cts = cts ?? new CancellationTokenSource())
await Task.Delay(3000, cts.Token).ContinueWith(tr =>
{
if (!tr.IsCanceled)
{
var st = searchText;
//Do search here
}
});
}
finally { cts = null; }
thanks, sorry i was a bit trigger happy, and didn't finish asking my question
You can't access the TreeView from any other thread than the dispatcher thread that it was originally created on, but you can make sure that the continuation action will be executed on this thread by using an overload that accepts a TaskScheduler:
await Task.Delay(3000, cts.Token).ContinueWith(tr =>
{
if (!tr.IsCanceled)
{
var st = searchText;
//Do search here
}
}, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());

Task async and continuewith

I'm trying to get data from server and afterwards I need to do few things with that data and other functions.
Because I'm getting data from server I did it with async and continuewith functions.
This is my code:
private void login(object sender, EventArgs eventArgs)
{
SharedFunctions.showHide("Show", pBar, txt);
result = false;
if (validateScreen())
{
Task task = new Task(() => LoginUser().ContinueWith((t) =>
{
afterLogin();
}));
task.Start();
}
}
private void afterLogin()
{
if (result)
{
SharedFunctions.saveDataOnDevice(userID, storeID, permission);
StartActivity(typeof(SplashScreen));
Finish();
}
else
{
SharedFunctions.showHide("Hide", pBar, txt);
SharedFunctions.showPopUp(this, GetString(Resource.String.error_login), GetString(Resource.String.wrong_name_and_password));
}
}
private async Task LoginUser()
{
string userName = uName.Text;
string password = pass.Text;
password = SharedFunctions.encrypt(password);
var client = new RestClient("........");
string resourceStr = #"api/base/....";
var request = new RestRequest(Method.POST)
{
Resource = resourceStr,
RequestFormat = DataFormat.Json
};
request.AddBody(new { UserName = userName, Password = password });
var response = await client.ExecuteTaskAsync<dynamic>(request);
var dt = response.Data;
if (dt != null)
{
userID = dt["ID"];
storeID = dt["StoreID"];
permission = dt["Permission"];
result = true;
}
else
result = false;
}
My main problem is that after I get the data, right after this code: if (dt != null).
When I try to debug the code it reaches the row with userID = dt["ID"]; and even before it is executed it jumps to the afterLogin() function.
What do I need to change in my code to make it run the entire functions before going to the next?
Thank you in advance!
As I describe on my blog, you should never use the Task constructor or the Start method. They are extremely outdated ways to execute code on a thread pool thread, which your app doesn't even need to do since LoginUser is asynchronous. If you did need to execute code on a thread pool thread, the correct API is Task.Run, but in this case you don't need it.
As a side note, you should not use ContinueWith in this situation, either (also explained on my blog). In fact, ContinueWith is downright dangerous; you should use await instead.
After applying these best practices:
private async void login(object sender, EventArgs eventArgs)
{
SharedFunctions.showHide("Show", pBar, txt);
result = false;
if (validateScreen())
{
await LoginUser();
afterLogin();
}
}
When it jumps out that means that the Task breaks (exception) but it doesn't break the whole program as this is an async Task.
Make a breakpoint right threre and check what is inside dt.
Especially with strings like "ID" you often have errors like these. It might be as well "Id" or "id" which is not equal nill but also not matching your "ID".
Good luck!
The problem was very different.
I found out that I cannot convert var that was in the dynamic object to int and that is why the program stopped.
I change this row: userID = dt["ID"]; to this: userID = Convert.ChangeType(dt["ID"], typeof(int));
And now it works.
Thank you all for all of your suggestions.
Task.ContinueWith starts only after completion of the original task.
I'm not sure if this'll help you but try this
private void login(object sender, EventArgs eventArgs)
{
SharedFunctions.showHide("Show", pBar, txt);
result = false;
if (validateScreen())
{
Task.Factory.StartNew(() => { LoginUser(); }).ContinueWith((t) => { afterLogin(); });
}
}
And make your login a normal void function.

Implementing correct completion of a retryable block

Teaser: guys, this question is not about how to implement retry policy. It's about correct completion of a TPL Dataflow block.
This question is mostly a continuation of my previous question Retry policy within ITargetBlock. The answer to this question was #svick's smart solution that utilizes TransformBlock (source) and TransformManyBlock (target). The only problem left is to complete this block in a right way: wait for all the retries to be completed first, and then complete the target block. Here is what I ended up with (it's just a snippet, don't pay too many attention to a non-threadsafe retries set):
var retries = new HashSet<RetryingMessage<TInput>>();
TransformManyBlock<RetryableMessage<TInput>, TOutput> target = null;
target = new TransformManyBlock<RetryableMessage<TInput>, TOutput>(
async message =>
{
try
{
var result = new[] { await transform(message.Data) };
retries.Remove(message);
return result;
}
catch (Exception ex)
{
message.Exceptions.Add(ex);
if (message.RetriesRemaining == 0)
{
if (failureHandler != null)
failureHandler(message.Exceptions);
retries.Remove(message);
}
else
{
retries.Add(message);
message.RetriesRemaining--;
Task.Delay(retryDelay)
.ContinueWith(_ => target.Post(message));
}
return null;
}
}, dataflowBlockOptions);
source.LinkTo(target);
source.Completion.ContinueWith(async _ =>
{
while (target.InputCount > 0 || retries.Any())
await Task.Delay(100);
target.Complete();
});
The idea is to perform some kind of polling and verify whether there are still messages that waiting to be processed and there are no messages that require retrying. But in this solution I don't like the idea of polling.
Yes, I can encapsulate the logic of adding/removing retries into a separate class, and even e.g. perform some action when the set of retries becomes empty, but how to deal with target.InputCount > 0 condition? There is not such a callback that get called when there are no pending messages for the block, so it seems that verifying target.ItemCount in a loop with a small delay is an only option.
Does anybody knows a smarter way to achieve this?
Maybe a ManualResetEvent can do the trick for you.
Add a public property to TransformManyBlock
private ManualResetEvent _signal = new ManualResetEvent(false);
public ManualResetEvent Signal { get { return _signal; } }
And here you go:
var retries = new HashSet<RetryingMessage<TInput>>();
TransformManyBlock<RetryableMessage<TInput>, TOutput> target = null;
target = new TransformManyBlock<RetryableMessage<TInput>, TOutput>(
async message =>
{
try
{
var result = new[] { await transform(message.Data) };
retries.Remove(message);
// Sets the state of the event to signaled, allowing one or more waiting threads to proceed
if(!retries.Any()) Signal.Set();
return result;
}
catch (Exception ex)
{
message.Exceptions.Add(ex);
if (message.RetriesRemaining == 0)
{
if (failureHandler != null)
failureHandler(message.Exceptions);
retries.Remove(message);
// Sets the state of the event to signaled, allowing one or more waiting threads to proceed
if(!retries.Any()) Signal.Set();
}
else
{
retries.Add(message);
message.RetriesRemaining--;
Task.Delay(retryDelay)
.ContinueWith(_ => target.Post(message));
}
return null;
}
}, dataflowBlockOptions);
source.LinkTo(target);
source.Completion.ContinueWith(async _ =>
{
//Blocks the current thread until the current WaitHandle receives a signal.
target.Signal.WaitOne();
target.Complete();
});
I am not sure where your target.InputCount is set. So at the place you change target.InputCount you can add following code:
if(InputCount == 0) Signal.Set();
Combining hwcverwe answer and JamieSee comment could be the ideal solution.
First, you need to create more than one event:
var signal = new ManualResetEvent(false);
var completedEvent = new ManualResetEvent(false);
Then, you have to create an observer, and subscribe to the TransformManyBlock, so you are notified when a relevant event happens:
var observer = new RetryingBlockObserver<TOutput>(completedEvent);
var observable = target.AsObservable();
observable.Subscribe(observer);
The observable can be quite easy:
private class RetryingBlockObserver<T> : IObserver<T> {
private ManualResetEvent completedEvent;
public RetryingBlockObserver(ManualResetEvent completedEvent) {
this.completedEvent = completedEvent;
}
public void OnCompleted() {
completedEvent.Set();
}
public void OnError(Exception error) {
//TODO
}
public void OnNext(T value) {
//TODO
}
}
And you can wait for either the signal, or completion (exhaustion of all the source items), or both
source.Completion.ContinueWith(async _ => {
WaitHandle.WaitAll(completedEvent, signal);
// Or WaitHandle.WaitAny, depending on your needs!
target.Complete();
});
You can inspect the result value of WaitAll to understand which event was set, and react accordingly.
You can also add other events to the code, passing them to the observer, so that it can set them when needed. You can differentiate your behaviour and respond differently when an error is raised, for example

Cancelling a task which retrieves URLs asynchronously

I'm having a bit of a problem finding out how to cancel this task in C#. I don't exactly have a strong understanding of handling threads and I've tried Googling for some simple code examples to help me out but I've gotten really no where. Here's the piece of code I'm working on:
var tasks = urls.Select(url => Task.Factory.StartNew(
state =>
{
using (var client = new WebClient())
{
lock (this)
{
// code to download stuff from URL
}
}
}, url)).ToArray();
try
{
Task.WaitAll(tasks);
}
catch (Exception e)
{
textBox2.AppendText("Error: " + e.ToString());
}
Where "urls" is an array of URLs. Is there a simple way to make it so that, when I click a button in my program, the downloading of the URLs is stopped completely? Also, the code snippet I pasted is in a function which backgroundWorker1 calls, which I suppose might make things a bit more complicated. (The reason why I have a backgroundWorker is so the UI doesn't lock up while it's downloading URLs.)
If that in any way is a bit confusing, here is an outline of what I was trying to achieve with my code:
I have an array of URLs, I'd like to download every URL asynchronously without locking up the UI.
I'd preferably like the user to stop the program from downloading URLs by clicking a button, pretty much cancelling the thread.
When the user clicks the button again, the program downloads the URLs all over again from that array.
Thanks in advance.
Don't know if this is right way to do this or not, but I have been able to cancel tasks using the following code. I have created a form with ListBox and ProgressBar so I'm raising and handling ProgressChanged event of BackgroundWorker. Hope this helps you in some way.
void bw_DoWork(object sender, DoWorkEventArgs e)
{
CancellationTokenSource _tokenSource = new CancellationTokenSource();
CancellationToken _token = _tokenSource.Token;
var urls = e.Argument as IEnumerable<string>;
_token = new CancellationToken();
if (urls == null) return;
var i = 0;
var a = 100 / urls.Count();
var sb = new StringBuilder();
var t = urls.Select(url => Task.Factory.StartNew(
(u) =>{
using (var wc = new WebClient())
{
lock (this){
var s = wc.DownloadString(u.ToString());
sb.AppendFormat("{1}:{0}\r\n", "", u);
}
}
if (Worker.CancellationPending){
//MAGIC HAPPENS HERE, IF BackgroundWorker IS REQUESTED
//TO CANCEL, WE CANCEL CancellationTokenSource
_tokenSource.Cancel();
}
//IF CANCELATION REQUESTED VIA CancellationTokenSource
//THROW EXCEPTION WHICH WILL ADD TO AggreegateException
_token.ThrowIfCancellationRequested();
//YOU CAN IGNORE FOLLOWING 2 LINES
i += a;
Worker.ReportProgress(i, u);
}, url, _token)).ToArray();
try
{
Task.WaitAll(t);
}
catch (AggregateException age)
{
if (age.InnerException is OperationCanceledException)
sb.Append("Task canceled");
}
catch (Exception ex)
{
sb.Append(ex.Message);
}
e.Result = sb;
}
With WebClient, you can use the CancelAsync method to cancel an asynchronous operation.
To cancel the tasks you're starting via Factory.StartNew, you should use a CancellationTokenSource. You need to pass CancellationTokenSource.Token to the tasks (and you can ask if the token is canceled already using token.IsCancellationRequested), and you'd call CancellationTokenSource.Cancel() to set the token as cancelled.

When exactly is a CallbackMethod invoked on an Async operation?

I am adding some data to a list and it might take a while to do this. Therefore i perform this action asynchronously. I do it like this:
ScanDelegate worker = StartScan;
AsyncCallback completedCallback = ScanCompletedCallback;
lock (_sync)
{
var async = AsyncOperationManager.CreateOperation(null);
worker.BeginInvoke(completedCallback, async);
}
The information added in StartScan() is correctly added to the list. When the scan is complete, i want to perform a different scan, depending on the data in the list. So i start the different scan in the ScanCompletedCallback() method. But at this point, my list is empty. Im guessing that this is because the callback method is invoked when the worker has been started, and not when it returns.
Is this true?
And if it is, how can i know when my worker has completed its tasks?
Edit: I can't get my head around this. It doesn't make sense. I came to think of the list i am adding to. I couldn't just add to it, i had to wrap it in a Dispatcher thread. This must be the problem, right? Is there a way i can make my Async method StartScan() wait for this Dispatcher ?
Thanks in advance!
StartScan()
private void StartScan()
{
//For testing
for (int t = 0; t < 1; t++)
{
var app2 = Application.Current;
if (app2 != null)
{
app2.Dispatcher.BeginInvoke(DispatcherPriority.Background,
new DispatcherOperationCallback(AddComputerToList),
new ComputerModel()
{
HostName = "Andreas-PC",
IpAddress = "192.168.1.99",
ActiveDirectoryPath = "ikke i AD",
Name = "Andreas-PC",
OperatingSystem = "Microsoft Windows 7 Enterprise"
});
}
}
}
ScanCompletedCallback()
private void ScanCompletedCallback(IAsyncResult ar)
{
var worker = (ScanDelegate)((AsyncResult)ar).AsyncDelegate;
var async = (AsyncOperation)ar.AsyncState;
worker.EndInvoke(ar);
lock (_sync)
{
IsScanning = false;
}
var completedArgs = new AsyncCompletedEventArgs(null, false, null);
async.PostOperationCompleted(e => OnScanCompleted((AsyncCompletedEventArgs)e), completedArgs);
}
AddComputerToList()
private object AddComputerToList(Object objComputer)
{
var computer = objComputer as ComputerModel;
if (computer != null)
{
ComputersList.Add(computer);
OnPropertyChanged("ComputersList");
}
return null;
}
As a direct answer to your question the callback will occur on completion of ScanCompletedCallback.
If you have a list that you believe should have some data in it at this point then there appears to be something else wrong, posting some more code may help with this.

Categories

Resources