Canceling search using a cancellationToken - c#

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

Related

System.Net.Http.HttpClient.PostAsync blocks and never returns

I have a .NET framework Windows Forms application with a form that has this code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace test
{
public partial class Main : Form
{
public int exitCode = 1;
private Options opts;
CancellationTokenSource cancellationSource = new CancellationTokenSource();
public Main(Options opts)
{
InitializeComponent();
this.opts = opts;
}
private void btnCancel_Click(object sender, EventArgs e)
{
exitCode = 1;
cancellationSource.Cancel();
Close();
}
async Task doUpload()
{
using (var content = new MultipartFormDataContent())
{
List<FileStream> streams = new List<FileStream>();
try
{
foreach (string fPath in opts.InputFiles)
{
FileStream stream = new FileStream(fPath, FileMode.Open, FileAccess.Read);
streams.Add(stream);
content.Add(new StreamContent(stream), fPath);
}
var progressContent = new ProgressableStreamContent(
content,
4096,
(sent, total) =>
{
double percent = 100 * sent / total;
progressBar.Value = (int)percent;
});
using (var client = new HttpClient())
{
using (var response = await client.PostAsync(opts.URL, progressContent, cancellationSource.Token))
{
if (response.IsSuccessStatusCode)
{
exitCode = 0;
}
else
{
MessageBox.Show(
response.Content.ToString(),
"Error " + response.StatusCode,
MessageBoxButtons.OK, MessageBoxIcon.Error
);
}
Close();
}
}
}
finally
{
foreach (FileStream stream in streams)
{
stream.Close();
}
}
}
}
private void Main_Load(object sender, EventArgs e)
{
}
private void Main_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = !cancellationSource.IsCancellationRequested;
}
private void Main_Shown(object sender, EventArgs e)
{
doUpload();
}
}
}
The ProgressableStreamContent is the same that was given here: C#: HttpClient, File upload progress when uploading multiple file as MultipartFormDataContent
The problem is that the response is never returned. In other words: await for postAsync never completes. Also, the progress callback is never called back. Even if I try to use a POST URL that contains a non-exsitent domain, nothing happens. I guess it is a deadlock, but I don't see how? The async Task's result is never used anywhere and it is not awaited for.
It is different from An async/await example that causes a deadlock because .Result is not used and the method is never awaited for, and also it seems that calling ConfigureAwait(false) ha no effect.
UPDATE: I have created a new github repo for this question, so anyone can test it:
https://github.com/nagylzs/csharp_http_post_example
UPDATE: Finally it works. ConfigureAwait is not needed. All UI update operations must be placed inside Invoke. I have updated the test repo to the working version. Also added TLSv1.2 support (which is disabled by default).
PostAsync in the code you've posted doesn't block (but it really never returns though!). It throws an exception:
System.InvalidOperationException: Cross-thread operation not valid: Control 'progressBar' accessed from a thread other than the thread it was created on.
That's the reason for the breakpoints that didn't worked for you. The right solution would be:
var progressContent = new ProgressableStreamContent(
content,
4096,
(sent, total) =>
{
Invoke((Action) (() => {
double percent = 100 * sent / total;
progressBar.Value = (int) percent;
}));
});
(either add Invoke or BeginInvoke to the callback)
The callbacks of the HTTP client are called on a background thread, and you have to put them into your window's even queue if you want them to access your UI controls.
.ConfigureAwait(false) has nothing to do with this issue, you shouldn't use it in UI context (quite the opposite: you want it to put the continuation onto the UI thread, so you shouldn't use it).
You need to change this:
client.PostAsync(opts.URL, progressContent, cancellationSource.Token)
to
client.PostAsync(opts.URL, progressContent, cancellationSource.Token).ConfigureAwait(false)
This is already discussed so you can find additional resources on the net, but this should be good starting point.

ASP.NET - async programming

I am trying to understand async programming, and I had a question. It is regarding the following functions below.
public async void TestAsyncCall() {
Task<string> TaskResult1 = DoSomethingAsync();
string Result2 = DoSomething();
string Result1 = await TaskResult1;
}
public string DoSomething() {
return "synch";
}
public async Task<string> DoSomethingAsync() {
await Task.Delay(10000);
return "asynch";
}
In the function call TestAsyncCall(), would one thread be used to execute DoSomethingAsync(), and another thread to execute DoSomething()?
Then when await is encountered, it would wait for DoSomethingAsync() to complete and release that thread (while also not blocking the original thread)?
Or will this not warrant any new threads being created? In that case will the DoSomethingAsync call be relevant only if it were to deal with some external resource?
I recommend you read my article on async ASP.NET.
Or will this not warrant any new threads being created?
This won't create any new threads. In particular, async and await by themselves won't create any new threads.
On ASP.NET, it's likely that the code after an await will run on a different thread than the code before that await. This is just exchanging one thread for another, though; no new threads are created.
In that case will the DoSomethingAsync call be relevant only if it were to deal with some external resource?
The primary use case for async is to deal with I/O, yes. This is particularly true on ASP.NET.
As #Stepehen-cleary said, "In particular, async and await by themselves won't create any new threads."
This next example is taken from the book: "C sharp in Depth" by John Skeet, chapter 15 pp.465:
class AsyncForm : Form
{
/* The first part of listing 15.1 simply creates the UI and hooks up an event handler for
the button in a straightforward way */
Label label;
Button button;
public AsyncForm()
{
label = new Label {
Location = new Point(10, 20),
Text = "Length"
};
button = new Button {
Location = new Point(10, 50),
Text = "Click"
};
button.Click += DisplayWebSiteLength;
AutoSize = true;
Controls.Add(label);
Controls.Add(button);
}
/* When you click on the button, the text of the book’s home page is fetched
and the label is updated to display the HTML lenght in characters */
async void DisplayWebSiteLength(object sender, EventArgs e)
{
label.Text = "Fetching...";
using (HttpClient client = new HttpClient())
{
string text =
await client.GetStringAsync("http://csharpindepth.com");
label.Text = text.Length.ToString();
}
}
/* The label is updated to display the HTML length in characters D. The
HttpClient is also disposed appropriately, whether the operation succeeds or fails—
something that would be all too easy to forget if you were writing similar asynchronous
code in C# 4 */
}
With this in mind, let's take a look to your code, you have Result1 and Result2, there's no point in having one asynchronous task waiting for a synchronous task to be finished. I would use Parallelism so you can perform both methods but to return something like two sets of Data, performing LINQ queries at the same time.
Take a look to this short example about Parallelism with Async Tasks:
public class StudentDocs
{
//some code over here
string sResult = ProcessDocs().Result;
//If string sResult is not empty there was an error
if (!sResult.Equals(string.Empty))
throw new Exception(sResult);
//some code over there
##region Methods
public async Task<string> ProcessDocs()
{
string sResult = string.Empty;
try
{
var taskStuDocs = GetStudentDocumentsAsync(item.NroCliente);
var taskStuClasses = GetStudentSemesterClassesAsync(item.NroCliente, vencimientoParaProductos);
//We Wait for BOTH TASKS to be accomplished...
await Task.WhenAll(taskStuDocs, taskStuClasses);
//Get the IList<Class>
var docsStudent = taskStuDocs.Result;
var docsCourses = taskStuClasses.Result;
/*
You can do something with this data ... here
*/
}
catch (Exception ex)
{
sResult = ex.Message;
Loggerdb.LogInfo("ERROR:" + ex.Message);
}
}
public async Task<IList<classA>> GetStudentDocumentsAsync(long studentId)
{
return await Task.Run(() => GetStudentDocuments(studentId)).ConfigureAwait(false);
}
public async Task<IList<classB>> GetStudentSemesterCoursessAsync(long studentId)
{
return await Task.Run(() => GetStudentSemesterCourses(studentId)).ConfigureAwait(false);
}
//Performs task to bring Student Documents
public IList<ClassA> GetStudentDocuments(long studentId)
{
IList<ClassA> studentDocs = new List<ClassA>();
//Let's execute a Stored Procedured map on Entity Framework
using (ctxUniversityData oQuery = new ctxUniversityData())
{
//Since both TASKS are running at the same time we use AsParallel for performing parallels LINQ queries
foreach (var item in oQuery.GetStudentGrades(Convert.ToDecimal(studentId)).AsParallel())
{
//These are every element of IList
studentDocs.Add(new ClassA(
(int)(item.studentId ?? 0),
item.studentName,
item.studentLastName,
Convert.ToInt64(item.studentAge),
item.studentProfile,
item.studentRecord
));
}
}
return studentDocs;
}
//Performs task to bring Student Courses per Semester
public IList<ClassB> GetStudentSemesterCourses(long studentId)
{
IList<ClassB> studentCourses = new List<ClassB>();
//Let's execute a Stored Procedured map on Entity Framework
using (ctxUniversityData oQuery = new ctxUniversityData())
{
//Since both TASKS are running at the same time we use AsParallel for performing parallels LINQ queries
foreach (var item in oQuery.GetStudentCourses(Convert.ToDecimal(studentId)).AsParallel())
{
//These are every element of IList
studentCourses.Add(new ClassB(
(int)(item.studentId ?? 0),
item.studentName,
item.studentLastName,
item.carreerName,
item.semesterNumber,
Convert.ToInt64(item.Year),
item.course ,
item.professorName
));
}
}
return studentCourses;
}
#endregion
}

Thread cancellation token for direct search bar

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.

Using Dispatcher correctly in event to update UI

I'm using dispatchers to update a bound collection from an event. I just ran into a nasty issue where I had two different dispatchers in the same event and it wasn't working. Using the debugger it was completely skipping over the code in the first dispatcher. Putting the entire event in a single dispatcher fixed it. I assume it's because of how the compiler handles it, can anyone confirm this - only one dispatcher per event, at least when dealing with the same elements?
Here is the code, when it gets to the await after (line == 0), it exits the function completely. Later, when line !=0 it runs the "Old style menu" fine. If I put all of the code in a single dispatcher, everything works fine.
private async void ProcessNLS(string parameters) // NET/USB List Info
{
if (parameters.Substring(0, 1) == "A" || (parameters.Substring(0, 1) == "U")) // ASCII x08/2010 Only
{
int line = Convert.ToInt32(parameters.Substring(1, 1));
string text = parameters.Substring(3);
// New Menu, Clear Old - Use Last Received/Holding Menu: See NLT bug
if (line == 0)
{
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
State.Menu.ServiceType = State.holdingMenu.ServiceType;
...
State.Menu.Items.Clear();
});
OnMenuTitleInfoChanged(new MenuTitleInfoChangedArgs(State.Menu));
// Replace Network Top with custom menu
if (State.Menu.LayerInfo == LayerTypes.NetworkTop)
{
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
State.Menu.Items.Clear();
});
...
}
// Get 1st Advanced Menu
if (Device.SupportsAdvancedMenus & State.Menu.LayerInfo != LayerTypes.NetworkTop)
{
...
}
}
// Old style menu
if (!Device.SupportsAdvancedMenus && State.Menu.LayerInfo != LayerTypes.NetworkTop)
{
NetworkMenuItem menuItem = new NetworkMenuItem(line, text);
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
State.Menu.Items.Add(menuItem);
});
OnMenuLoading(new MenuLoadingArgs(menuItem));
}
}
// C - Track Cursor
if (parameters.Substring(0,1) == "C")
{
if (parameters.Substring(1, 1)== "-")
{
// No Cursor
// Sent when entering player screen
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
...
State.Menu.Items.Clear();
OnMenuTitleInfoChanged(new MenuTitleInfoChangedArgs(State.Menu));
}
}
});
}
Like this it would just jump over the dispatcher for no apparent reason. If I put the entire thing in a single dispatcher it works fine.
A second question, if I have another event with a dispatcher, something like this:
foreach (xxx)
{
if (xxx == yyy)
{
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
{
State.Menu.Items.Add(menuItem);
});
}
}
Would it be preferable to instead wrap the entire foreach loop in a dispatcher rather then calling it when needed each iteration?
Since my original question has changed I've made a new post with more specifics and another possible solution by just wrapping the original socket listener task in a dispatcher
Possible solution to issue with multiple UI dispatchers in the same method?
*** Update:
I think Raymond is on the right track, though adding Task didn't fix it, I noticed although it starts processing line "0" of the menu, before it sets up the new menu it tries to process the next line "1" command which is ignored because it doesn't have the right menu state yet, it still hasn't been set by the previous command yet.
I'm not sure how to fix it, it seems like I have to do an await at a lower level so be sure sure it full finishes one command before starting the next (and not sure why putting the whole ProcessNLS in UI dispatcher works), it's a little complicated since I go through multiple levels but here is the flow:
socket = new StreamSocket();
try
{
await socket.ConnectAsync(new HostName(HostName), Port);
OnConnect(new EventArgs());
await Task.Factory.StartNew(WaitForMessage);
}
catch (Exception ex)
{
OnConnectionFail(new EventArgs());
}
Goes to:
private async void WaitForMessage()
{
...
foreach (var message in messages)
{
if (string.IsNullOrWhiteSpace(message))
continue;
ProcessMessage(message);
}
}
Goes to
private void ProcessMessage(string message, string optionalFlags = "")
{
...
case "NLS": // NET/USB List Info
ProcessNLS(parameters);
break;
}
to finally
private async void ProcessNLS(string parameters) // NET/USB List Info
My alternate solution is to put to ProcessMessage call under WaitForMessage in a UI dispatcher
*** Update #2
I think this may be working, here is the updated flow, have to await multiple steps, use task instead of void
private async void WaitForMessage()
{
...
foreach (var message in messages)
{
if (string.IsNullOrWhiteSpace(message))
continue;
await ProcessMessage(message);
}
}
}
catch (Exception ex)
{
Debug.WriteLine("WaitForMessage Error: " + ex.Message);
OnDisconnect(new EventArgs());
}
}
to
private async Task ProcessMessage(string message, string optionalFlags = "")
{
...
case "NLS": // NET/USB List Info
await ProcessNLS(parameters);
break;
}
to
private async Task ProcessNLS(string parameters) // NET/USB List Info
The problem is here:
private async void ProcessNLS(...)
^^^^^^^^^^
You declared an async void function, which means "When the first await happens, return from the function immediately, and let the rest of the work run asynchronously." If you want the caller to be able to await on completion of your function, change the signature to private async Task ProcessNLS(...).

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.

Categories

Resources