Cancel Parallel.ForEach or use async await - c#

I have this event:
private void TextBoxSearchText_TextChanged(object sender, TextChangedEventArgs e)
{
searchText();
}
and I want to cancel this parallel method and start a new one when textbox text changes and also want my textbox be responsive to my new text typing, which is lock until results come to listbox.
List<TextList> oSelected;
private void searchText()
{
string strSearchText = TextBoxSearchText.Text;
oSelected = new List<TextList>();
Parallel.ForEach(oTextList, item =>
{
Match myMatch = Regex.Match(item.EnglishText.ToString(), "\\b" + strSearchText.ToString().ToLower() + #"\w*", RegexOptions.IgnoreCase);
if (!myMatch.Success)
{
return;
}
oSelected.Add(new TextList
{
Id = item.Id,
EnglishText = item.EnglishText
});
});
ListBoxAllTexts.ItemsSource = oSelected;
}
Is it possible to use async and awiat to accomplish the job?
Which one is better for searching a text in almost 1 million line of text?
I read alot about async and await but I couldn't understand how to use it in my work.
Thank you

Since your work is CPU-bound, you should use parallel code to do the actual search. However, you can wrap the parallel work in an async/await mindset by using Task.Run:
private async void TextBoxSearchText_TextChanged(object sender, TextChangedEventArgs e)
{
ListBoxAllTexts.ItemsSource = await Task.Run(() => searchText(TextBoxSearchText.Text));
}
This will keep your UI responsive.
To do cancellation, use a CancellationTokenSource. On a side note, you can't update a List<T> from a parallel loop like you're currently trying to do because List<T> is not threadsafe. In this scenario, I recommend you use PLINQ instead:
private CancellationTokenSource _cts;
private async void TextBoxSearchText_TextChanged(object sender, TextChangedEventArgs e)
{
if (_cts != null)
_cts.Cancel();
_cts = new CancellationTokenSource();
var strSearchText = TextBoxSearchText.Text;
ListBoxAllTexts.ItemsSource = await Task.Run(
() => searchText(strSearchText, _cts.Token));
}
private List<TextList> searchText(string strSearchText, CancellationToken token)
{
try
{
return oTextList.AsParallel().WithCancellation(token)
.Where(item => Regex.IsMatch(item.EnglishText.ToString(), "\\b" + strSearchText.ToLower() + #"\w*", RegexOptions.IgnoreCase))
.Select(item => new TextList
{
Id = item.Id,
EnglishText = item.EnglishText
})
.ToList();
}
catch (OperationCanceledException)
{
return null;
}
}
Also, consider throttling the user input by only starting the search after a delay. Rx is the best way to do that.

Related

get long process progress in async task function

I have a async function and used the Progress< int>
to progress long process After I run another task, it seems that the many progress reports are running.
private async void Listbox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var progress = new Progress<int>(percent =>
{
prg.Value = percent;
});
isCanceled = true;
await ExecuteManuallyCancellableTaskAsync(progress);
}
and this is my func
public async Task ExecuteManuallyCancellableTaskAsync(IProgress<int> progress)
{
var mprogress = 0;
prg.Value = 0;
using (var cancellationTokenSource = new CancellationTokenSource())
{
cancellationTokenSource.Cancel();
var SearchTask = Task.Run(async () =>
{
foreach (var file in await GetFileListAsync(GlobalData.Config.DataPath))
{
if (isCanceled)
{
cancellationTokenSource.Cancel();
return;
}
mprogress += 1;
progress.Report((mprogress * 100 / TotalItem));
await Dispatcher.InvokeAsync(() =>
{
// my codes
}, DispatcherPriority.Background);
}
});
await SearchTask;
}
}
and this is result that you can see different value in progressbar same time.
In short you are not using the CancellationTokenSource correctly for 2 reasons; firstly it needs to be passed around to any async methods that you are calling in order to truly cancel them, secondly it will likely need to live for longer than just inside the scope where you are using it.
Try something like this (complete with comments to hopefully make it easy to follow):
private CancellationTokenSource cancellationTokenSource; // And remove isCanceled as this is causing some of the issues
private async void Listbox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var progress = new Progress<int>(percent =>
{
prg.Value = percent;
});
// Make sure any current processing is stopped.
cancellationTokenSource?.Cancel();
// Prepare to be able to cancel the next round of processing.
cancellationTokenSource = new CancellationTokenSource();
await ExecuteManuallyCancellableTaskAsync(progress, cancellationTokenSource.Token);
}
public async Task ExecuteManuallyCancellableTaskAsync(IProgress<int> progress, CancellationToken cancelToken)
{
var mprogress = 0;
prg.Value = 0;
await Task.Run(async () =>
{
// You will need to implement checks against the CancellationToken in your GetFileListAsync method also.
foreach (var file in await GetFileListAsync(GlobalData.Config.DataPath, cancelToken))
{
mprogress += 1;
progress.Report((mprogress * 100 / TotalItem));
// Only update the UI if we have not been requested to cancel.
if (!cancelToken.IsCancellationRequested)
{
await Dispatcher.InvokeAsync(() =>
{
// my codes
}, DispatcherPriority.Background);
}
}
}, cancelToken); // Pass in the token to allow the Task to be cancelled.
}

WPF UI not updating and not sure why?

I had a timer in my app that was causing too many thread locks so I decided to replace the timer with a task that runs in a loop. Now, when using the task my UI properties are not updating correctly. I figured it was a thread issue so I move the UI updates outside of the task and used await, but the UI still isn't working. In debug I following the code to the UI and it's on the main thread with the correct data but the UI never updates. The symptom is a frozen UI and a process memory that just keep growing. I"m lost on how to fix this one. Maybe someone can help?
private static void OnStaticPropertyChanged([CallerMemberName] string propertyName = null)
{ StaticPropertyChanged?.Invoke(null, new propertyChangedEventArgs(propertyName));
}
private void PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Dispatcher.BeginInvoke((Action)(() =>
{
switch (e.PropertyName)
{
case "SiderealTime":
TextLst.Content = _util.HoursToHMS(TelescopeHardware.SiderealTime);
break;
case "RightAscension":
TextRa.Content = _util.HoursToHMS(TelescopeHardware.RightAscension, "h ", "m ", "s", 3);
break;
}));
}
private static async void Main_Mount_Loop()
{
_cts = new CancellationTokenSource();
var ct = _cts.Token;
var keepGoing = true;
while (keepGoing)
{
var task = Task.Run(() =>
{
ct.ThrowIfCancellationRequested();
Task.Delay(1000, ct);
MoveAxes();
if (ct.IsCancellationRequested)
{
// Clean up here, then...
keepGoing = false;
}
}, ct);
await task;
UpdateUi();
}
}
Reactive extensions seem to be a more suited library for this usage:
Observable
.Interval(TimeSpan.FromSeconds(1), ThreadPoolScheduler.Instance)
.Do(_ => MoveAxes())
.SubscribeOn(DispatcherScheduler.Current)
.Subscribe(_ => UpdateUi());

How I can write same code using Delegate/Callback?

Here is a very simple example that is available on the internet of Async/Await concept, I wonder how this little logic can be implemented using Begin/End Invoke?
All I need to do is prepare a complicated logic using both approaches (Async/Await and delegates), So I want to get a start from a basic workflow.
int countCharacters()
{
int count = 0;
using (StreamReader reader= new StreamReader("D:\\Data.txt"))
{
string content = reader.ReadToEnd();
count = content.Length;
Thread.Sleep(5000);
}
return count;
}
private async void btnProcessFIle_Click(object sender, EventArgs e)
{
Task<int> task = new Task<int>(countCharacters);
task.Start();
int count = await task;
lblCount.Text = "No. of characters in file=" +Environment.NewLine+ count.ToString();
}
Here is a very simple example that is available on the internet of Async/Await concept
This is a very bad example.
It uses the task constructor and Start, which is a clear no-no (there are literally no valid use cases to do this).
It also uses the filesystem synchronously on a background thread in an "async example".
If you want an example of how to consume synchronous (e.g., CPU-bound) code asynchronously, then this is a much better example that pretty much does the same thing:
int countCharacters()
{
Thread.Sleep(5000);
return 13;
}
private async void btnProcessFIle_Click(object sender, EventArgs e)
{
var count = await Task.Run(() => countCharacters());
lblCount.Text = "No. of characters in file=" + count;
}
Note that this is an example of how to call CPU-bound code from the UI thread asynchronously - it is not an example of the "asynchronous concept".
I wonder how this little logic can be implemented using Begin/End Invoke?
Since your work is synchronous work on a background thread, that's actually pretty easy; you can just use Delegate.BeginInvoke instead of creating your own IAsyncResult (which is the really hard part about APM - if you do need to implement it, see "Implementing the CLR Asynchronous Programming Model" in the 2007-03 issue of MSDN Magazine).
But since you can just use Delegate.BeginInvoke, it's pretty straightforward:
private void btnProcessFIle_Click(object sender, EventArgs e)
{
var ui = SynchronizationContext.Current;
Func<int> d = countCharacters;
d.BeginInvoke(CountCharactersCallback, ui);
}
private void CountCharactersCallback(IAsyncResult ar)
{
var d = (Func<int>) ((AsyncResult) ar).AsyncDelegate;
var ui = (SynchronizationContext) ar.AsyncState;
try
{
var count = d.EndInvoke(ar);
ui.Post(CountCharactersComplete, count);
}
catch (Exception ex)
{
var edi = ExceptionDispatchInfo.Capture(ex);
ui.Post(CountCharactersError, state);
}
}
private void CountCharactersComplete(object state)
{
var count = (int) state;
lblCount.Text = "No. of characters in file=" + count;
}
private void CountCharactersError(object state)
{
var edi = (ExceptionDispatchInfo)state;
edi.Throw();
}
Notes:
CountCharactersCallback is a "bare callback". Any exceptions propagated from CountCharactersCallback indicate a catastrophic error.
In particular, one must be careful not to allow exceptions from EndInvoke to propagate out of the BeginInvoke callback. This is a common mistake.
I'm using SynchronizationContext to sync back to the UI thread. This is the same behavior as await.
I'm using ExceptionDispatchInfo to preserve the exception stack trace across threads (without requiring a wrapper exception).
The CountCharactersError just raises the exception directly on the message loop. This is the same behavior as async void.
Feature BeginInvoke is access to the UI elements inside non-UI thread, so you need change your code like this:
private async void btnProcessFIle_Click(object sender, EventArgs e)
{
var task = Task.Run(() => {
var count = countCharacters();
lblCount.BeginInvoke((Action) (() =>
{
lblCount.Text = "No. of characters in file=" +Environment.NewLine+
count.ToString();
}));
});
await task;
}
Code without async / await. You don't need to use BeginInvoke because you can capture the context with TaskScheduler.FromCurrentSynchronizationContext()
int countCharacters()
{
int count = 0;
using (StreamReader reader= new StreamReader("D:\\Data.txt"))
{
string content = reader.ReadToEnd();
count = content.Length;
Thread.Sleep(5000);
}
return count;
}
private void btnProcessFIle_Click(object sender, EventArgs e)
{
Task<int> task = new Task<int>(countCharacters);
task.Start();
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
task.ContinueWith(count =>{
lblCount.Text = "No. of characters in file=" +Environment.NewLine+ count.ToString();
}, uiScheduler);
}
If you really want to use BeginInvoke I guess you could write
private void btnProcessFIle_Click(object sender, EventArgs e)
{
Task<int> task = new Task<int>(countCharacters);
task.Start();
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
task.ContinueWith(count =>{
lblCount.BeginInvoke(()=>{
lblCount.Text = "No. of characters in file=" +Environment.NewLine+ count.ToString();
});
});
}

Waiting on the results of GetGeopositionAsync()

I'm developing a WinPhone 8 App.
On this App there is a Button 'Send SMS'.
When the user clicks on this button two things should happen:
(Method A) Get the geo-coordinate of the current Location (using Geolocator and GetGeopositionAsync).
(Method B) Compose and send an SMS with the geo-coordinate as part of the body.
The Problem: GetGeopositionAsync is an asynchronous method. Before the coordinate is detected (which takes a few seconds) the SMS is sent (of course with no coordinates).
How can I tell Method 2 to wait until the coordinates are available?
OK, here is my code:
When the user presses the button, the coordinates are determined by the first method and the second method sends the SMS which includes the coordinates in its body:
private void btnSendSms_Click(object sender, RoutedEventArgs e)
{
GetCurrentCoordinate(); // Method 1
// -> Gets the coordinates
SendSms(); // Method 2
// Sends the coordinates within the body text
}
The first method GetCurrentCoordinate() looks as follows:
...
private GeoCoordinate MyCoordinate = null;
private ReverseGeocodeQuery MyReverseGeocodeQuery = null;
private double _accuracy = 0.0;
...
private async void GetCurrentCoordinate()
{
Geolocator geolocator = new Geolocator();
geolocator.DesiredAccuracy = PositionAccuracy.High;
try
{
Geoposition currentPosition = await geolocator.GetGeopositionAsync(
TimeSpan.FromMinutes(1),
TimeSpan.FromSeconds(10));
lblLatitude.Text = currentPosition.Coordinate.Latitude.ToString("0.000");
lblLongitude.Text = currentPosition.Coordinate.Longitude.ToString("0.000");
_accuracy = currentPosition.Coordinate.Accuracy;
MyCoordinate = new GeoCoordinate(
currentPosition.Coordinate.Latitude,
currentPosition.Coordinate.Longitude);
if (MyReverseGeocodeQuery == null || !MyReverseGeocodeQuery.IsBusy)
{
MyReverseGeocodeQuery = new ReverseGeocodeQuery();
MyReverseGeocodeQuery.GeoCoordinate = new GeoCoordinate(
MyCoordinate.Latitude,
MyCoordinate.Longitude);
MyReverseGeocodeQuery.QueryCompleted += ReverseGeocodeQuery_QueryCompleted;
MyReverseGeocodeQuery.QueryAsync();
}
}
catch (Exception)
{ // Do something }
}
private void ReverseGeocodeQuery_QueryCompleted(object sender,
QueryCompletedEventArgs<IList<MapLocation>> e)
{
if (e.Error == null)
{
if (e.Result.Count > 0)
{
MapAddress address = e.Result[0].Information.Address;
lblCurrAddress.Text = address.Street + " " + address.HouseNumber + ",\r" +
address.PostalCode + " " + address.City + ",\r" +
address.Country + " (" + address.CountryCode + ")";
}
}
}
}
And the Methode 'SendSms()':
private void SendSms()
{
SmsComposeTask smsComposeTask = new SmsComposeTask();
smsComposeTask.To = "0123456";
smsComposeTask.Body = "Current position: \rLat = " + lblLatitude.Text +
", Long = " + lblLongitude.Text +
"\r" + lblCurrAddress.Text;
// -> The TextBoxes are still empty!
smsComposeTask.Show();
}
The problem is, that all these TextBoxes (lblLatitude, lblLongitude, lblCurrAddress) are still empty when the method SendSms() sets the SmsComposeTask object.
I have to ensure that the TextBoxes are already set BEFORE the method SendSms() starts.
You should almost never mark a method async void unless it's a UI event handler. You're calling an asynchronous method without waiting for it to end. You are basically calling those 2 methods in parallel, so it's clear why the coordinates aren't available.
You need to make GetCurrentCoordinate return an awaitable task and await it, like this:
private async Task GetCurrentCoordinateAsync()
{
//....
}
private async void btnSendSms_Click(object sender, RoutedEventArgs e)
{
await GetCurrentCoordinateAsync();
// You'll get here only after the first method finished asynchronously.
SendSms();
}
This is one of the primary reasons you should avoid async void. void is a very unnatural return type for async methods.
First, make your GetCurrentCoordinate an async Task method instead of async void. Then, you can change your click handler to look like this:
private async void btnSendSms_Click(object sender, RoutedEventArgs e)
{
await GetCurrentCoordinate();
SendSms();
}
Your click handler is async void only because event handlers have to return void. But you should really strive to avoid async void in all other code.
There two things you're doing wrong here:
Using void returning async methods when you need to await on them. This is bad because you can't await on execution of these methods and should only be used when you can't make the method return Task or Task<T>. That's why you're not seeing anything on the text boxes when SendSmsis called.
Mixing UI and non-UI code. You should transfer data between UI and non-UI code to avoid tight coupling between code with different responsibilities. IT also makes it easy to read and debug the code.
ReverseGeocodeQuery does not have an awaitable async API but you can easily make your own:
private async Task<IList<MapLocation>> ReverseGeocodeQueryAsync(GeoCoordinate geoCoordinate)
{
var tcs = new TaskCompletionSource<IList<MapLocation>>();
EventHandler<QueryCompletedEventArgs<IList<MapLocation>>> handler =
(s, e) =>
{
if (e.Cacelled)
{
tcs.TrySetCancelled();
}
else if (e.Error != null)
{
tcs.TrySetException(e.Error);
}
else
{
tcs.TrySetResult(e.Result);
}
};
var query = new ReverseGeocodeQuery{ GeoCoordinate = geoCoordinate };
try
{
query.QueryCompleted += handler;
query.QueryAsync();
return await tcs.Task;
}
finally
{
query.QueryCompleted -= handler;
}
}
This way you'll get full cancellation and error support.
Now let's make the retrieval of the geo coordinate information all in one chunk:
private async Task<Tuple<Geocoordinate, MapLocation>> GetCurrentCoordinateAsync()
{
try
{
var geolocator = new Geolocator
{
DesiredAccuracy = PositionAccuracy.High
};
var currentPosition = await geolocator.GetGeopositionAsync(
TimeSpan.FromMinutes(1),
TimeSpan.FromSeconds(10))
.ConfigureAwait(continueOnCapturedContext: false);
var currentCoordinate = currentPosition.Coordinate;
var mapLocation = await this.ReverseGeocodeQueryAsync(
new GeoCoordinate(
currentCoordinate.Latitude,
currentCoordinate.Longitude));
return Tuple.Create(
currentCoordinate,
mapLocation.FirstOrDefault());
}
catch (Exception)
{
// Do something...
return Tuple.Create(null, null);
}
}
Now the button eventnt handler becomes much more readable:
private void btnSendSms_Click(object sender, RoutedEventArgs e)
{
var info = await GetCurrentCoordinate();
if (info.Item1 != nuil)
{
lblLatitude.Text = info.Item1.Latitude.ToString("0.000");
lblLongitude.Text = info.Item1.Longitude.ToString("0.000");
}
if (info.Item2 != null)
{
var address = info.Item2.Information.Address;
lblCurrAddress.Text = string.Format(
"{0} {1},\n{2} {3},\n{4} ({5})",
address.Street,
address.HouseNumber,
address.PostalCode,
address.City,
address.Country,
address.CountryCode);
}
SendSms(info.Item1, info.Item2);
}
Does this make sense?

C# and Tasks - UI Thread Hang - Pre-Async/Await keywords

I'm trying to understand what the correct code to grab a set of data asynchronously when I do not have access to the client lib I am using to retrieve the data. I specify an endpoint and a date range and I'm supposed to retrieve a list of playlists. What I have now never comes back after the Start() call. Note: this is running in a WinForm. I am trying to better understand Tasks and don't just want to jump to awaits or a BackgroundWorker. I know I'm getting lost somewhere.
private void GoButtonClick(object sender, EventArgs e)
{
string baseUrl = "http://someserver/api";
var startDateTime = this._startDateTimePicker.Value;
var endDateTime = this._endDateTimePicker.Value;
_getPlaylistsFunc = delegate()
{
var client = new PlaylistExportClient(baseUrl);
return client.GetPlaylistsByDateRange(startDateTime, endDateTime).ToList();
};
var task = new Task<List<Playlist>>(_getPlaylistsFunc);
task.ContinueWith((t) => DisplayPlaylists(t.Result));
task.Start();
}
private void DisplayPlaylists(List<Playlist> playlists)
{
_queueDataGridView.DataSource = playlists;
}
UPDATE
I made these changes but now the application seems to hang the UI thread.
private void GoButtonClick(object sender, EventArgs e)
{
string baseUrl = "http://someserver/api";
var startDateTime = this._startDateTimePicker.Value;
var endDateTime = this._endDateTimePicker.Value;
var token = Task.Factory.CancellationToken;
var context = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
var client = new PlaylistExportClient(baseUrl);
_queueDataGridView.DataSource = client.GetPlaylistsByDateRange(startDateTime, endDateTime).ToList();
},token,TaskCreationOptions.None,context);
}
I recommend you use the Task-based Asynchronous Pattern. It's quite straightforward:
private async void GoButtonClick(object sender, EventArgs e)
{
string baseUrl = "http://someserver/api";
var startDateTime = this._startDateTimePicker.Value;
var endDateTime = this._endDateTimePicker.Value;
var playlists = await Task.Run(() =>
{
var client = new PlaylistExportClient(baseUrl);
return client.GetPlaylistsByDateRange(startDateTime, endDateTime).ToList();
});
_queueDataGridView.DataSource = playlists;
}
Note that this will block a threadpool thread; if you can modify the library to have a GetPlaylistsByDateRangeAsync method, you can make this more efficient.
Edit: If you're stuck on .NET 4.0, you can install Microsoft.Bcl.Async to get full async/await capabilities. If - for some inexplicable reason - you still can't use async/await, then you can do it like this:
private void GoButtonClick(object sender, EventArgs e)
{
string baseUrl = "http://someserver/api";
var startDateTime = this._startDateTimePicker.Value;
var endDateTime = this._endDateTimePicker.Value;
var context = TaskScheduler.FromCurrentSynchronizationContext();
Task.Run(() =>
{
var client = new PlaylistExportClient(baseUrl);
return client.GetPlaylistsByDateRange(startDateTime, endDateTime).ToList();
}).ContinueWith(t =>
{
_queueDataGridView.DataSource = t.Result;
}, context);
}
However, note that your error handling is more complex with this approach.
It looks like you're assigning to a property of a UI control in a background thread. That's usually bad news. WPF usually throws an exception when you do that, not sure about WinForms.
Capture the data in the background thread, but switch back to the main UI thread before assigning it to a UI control. Try posting the data to the UI thread using something like
var uiSync = SynchronizationContext.Current;
Task.Factory.StartNew(() =>
{
var client = new PlaylistExportClient(baseUrl);
var list = client.GetPlaylistsByDateRange(...).ToList();
uiSync.Post(() => _queueDataGridView.DataSource = list, null);
},token,TaskCreationOptions.None,context);

Categories