How can I stop the Task.Run()? - c#

I'm newer to the concept of threading and I would like to use Task that is a component of Thread in my application because the save task takes time for executing.
This is my code:
private void SaveItem(object sender, RoutedEventArgs e)
{
// Button Save Click ( Save to the database )
Task.Run(() =>
{
var itemsS = Gridview.Items;
Dispatcher.Invoke(() =>
{
foreach (ItemsModel item in itemsS)
{
PleaseWaittxt.Visibility = Visibility.Visible;
bool testAdd = new Controller().AddItem(item);
if (testAdd)
Console.WriteLine("Add true to Items ");
else
{
MessageBox.Show("Add failed");
return;
}
}
PleaseWaittxt.Visibility = Visibility.Hidden;
});
});
MessageBox.Show("Save Done");
// update the gridView
var results = new Controller().GetAllItems();
Gridview.ItemsSource = null;
Gridview.ItemsSource = results;
Gridview.Items.Refresh();
}
The problem is that when I save all items, I got duplicate data in the database. Otherwise, the count of ItemsS is fixed to 300, but after the saving, I got 600,
Did Task.Run() repeat the save task to the database ?
NB: I'm working on UI project ( WPF Desktop app )

I'm thinking you'd need something along the lines of this.
I quickly whipped it up but i hope its enough to attempt a fix yourself.
private async void SaveItem(object sender, RoutedEventArgs e)
{
try {
var itemsS = GridviewServices.Items.ToList(); // to list makes shallow copy
await Task.Run(() => {
foreach (ItemsModel item in itemsS)
{
bool testAdd = new Controller().AddItem(item);
}
});
// Dont update ui in task.run, because only the ui thread may access UI items
// Do so here - after the await. (or use dispatcher.invoke).
GridviewServices.Items.Clear();
GridviewServices.Items = itemsS;
} catch { ... } // Handle exceptions, log them or something. Dont throw in async void!
}
I'm also thinking this would work:
private async void SaveItem(object sender, RoutedEventArgs e)
{
// Button Save Click ( Save to the database )
var itemsS = GridviewServices.Items;
await Task.Run(() =>
{
foreach (ItemsModel item in itemsS)
{
Dispatcher.Invoke(() => {PleaseWaittxt.Visibility = Visibility.Visible;})
bool testAdd = new Controller().AddItem(item);
if (testAdd)
Console.WriteLine("Add true to Items ");
else
{
MessageBox.Show("Add failed");
return;
}
}
Dispatcher.Invoke(() => {PleaseWaittxt.Visibility = Visibility.Hidden;})
});
MessageBox.Show("Save Done");
// update the gridView
var results = new Controller().GetAllItems();
Gridview.ItemsSource = null;
Gridview.ItemsSource = results;
Gridview.Items.Refresh();
}

The problem you're running in to, is because the Task you're executing isn't running in parallel, but synchronously to the rest of your application.
When you're running CPU-intensive tasks in the background of your UI-application, you'll want to either work with actual threads or async/await - which is what you attempted with your code.
What you'll want to do is something similar to this:
private async void SaveItem(object sender, RoutedEventArgs e) => await Task.Run(
/*optionally make this async too*/() => {
// Execute your CPU-intensive task here
Dispatcher.Invoke(() => {
// Handle your UI updates here
});
});
This is just a general overview, I don't know your exact use-case, but this should get you started in the right direction.
One thing to be weary of when using Lambdas and such, is closures.
If your application tends to use a lot of memory, you might want to re-think the structure of your calltree and minimize closures in your running application.

Related

Windows Show Desktop causes WinForms elements to stop updating (2)

Is there a simple way to have elements on a form keep updating even after I click on Windows Show Desktop? The following code updates the value in textBox1 until I click on Windows Show Desktop (Windows 10 - click on the bottom right of the screen). I prefer not to use Application.DoEvents()
private async void Button1Click(object sender, EventArgs e)
{
int n = 0;
while (true) {
Task<int> task = Increment(n);
var result = await task;
n = task.Result;
textBox1.Text = n.ToString();
textBox1.Refresh();
Update();
// await Task.Delay(200);
}
}
public async Task<int> Increment(int num)
{
return ++num;
}
One way to solve this problem is to offload the CPU-bound work to a ThreadPool thread, by using the Task.Run method:
private async void Button1Click(object sender, EventArgs e)
{
int n = 0;
while (true)
{
n = await Task.Run(() => Increment(n));
textBox1.Text = n.ToString();
}
}
This solution assumes that the Increment method does not interact with UI components internally in any way. If you do need to interact with the UI, then the above approach is not an option.

How to run multiple tasks in parallel and update UI thread with results?

I have a method which consists of two lists (1. items to search and 2. workers to search with). Each worker takes an item from the list, searches for it, and add the results to a global results list which update the UI thread (a listview).
This is what I came up with so far:
List<Result> allResults = new List<Result>();
var search = new Search(workers);
//Will be full with items to search for
var items= new ConcurrentBag<item>();
while (items.Any())
{
foreach (var worker in workers)
{
if (!items.Any())
break;
IEnumerable<Result> results = null;
Task.Factory.StartNew(() =>
{
if (ct.IsCancellationRequested)
return;
items.TryTake(out Item item);
if (item == null)
return;
results= search.DoWork(worker, item);
}, ct);
if (results?.Any() ?? false)
{
allResults.AddRange(reults);
}
//Update UI thread here?
}
}
The workers should search in parallel and their results added to the global results list. This list will then refresh the UI.
Am I on the right track with the above approach? Will the workers run in parallel? Should I update the UI thread within the task and use BeginInvoke?
This will run parallel searches from the list items up to a specified number of workers without blocking the UI thread and then put the results into a list view.
private CancellationTokenSource _cts;
private async void btnSearch_Click(object sender, EventArgs e)
{
btnSearch.Enabled = false;
lvSearchResults.Clear();
_cts = new CancellationTokenSource();
AddResults(await Task.Run(() => RunSearch(GetItems(), GetWorkerCount(), _cts.Token)));
btnSearch.Enabled = true;
}
private void btnCancel_Click(object sender, EventArgs e)
{
_cts?.Cancel();
}
private List<Result> RunSearch(List<Item> items, int workerCount, CancellationToken ct)
{
ConcurrentBag<List<Result>> allResults = new ConcurrentBag<List<Result>>();
try
{
Parallel.ForEach(items, new ParallelOptions() { MaxDegreeOfParallelism = workerCount, CancellationToken = ct }, (item) =>
{
Search search = new Search(); // you could instanciate this elseware as long as it's thread safe...
List<Result> results = search.DoWork(item);
allResults.Add(results);
});
}
catch (OperationCanceledException)
{ }
return allResults.SelectMany(r => r).ToList();
}
private void AddResults(List<Result> results)
{
if (results.Count > 0)
lvSearchResults.Items.AddRange(results.Select(r => new ListViewItem(r.ToString())).ToArray());
}
If your are working with Windows form, you can refer to How do I update the GUI from another thread?
If you are working with WPF. You can find your UI Dispatcher and use the dispatcher to update UI. Usually, even you try to update UI in a loop, it may not update the UI immediately. If you want to force to update UI, you can use DoEvents() method. The DoEvents() method also works for WPF. But try to avoid using DoEvents().

update text on a WPF page with delays

I new to WPF, and have to put a basic application together
It consists of one main window with a frame, and one page
the page has a basic status text -
the requirement is that when the page loads up, the application has to do a bunch of REST call to fetch some data from remote source, and update the status text as it fetches
problem is, as I update the text, it doesn't seem to be reflected on the page, or maybe it's being blocked - even though I've used Task
so far, I have the following code for testing:
private void Page_Loaded(object sender, RoutedEventArgs e) {
var wnd = Window.GetWindow(this);
wnd.ContentRendered += Wnd_ContentRendered;
}
private void Wnd_ContentRendered(object sender, EventArgs e) {
DisplayMessages();
}
private void DisplayMessages() {
authenticationText.Text = "text one";
var t = Task.Delay(5000);
t.Wait();
authenticationText.Text = "text two";
t = Task.Delay(5000);
t.Wait();
authenticationText.Text = "text three";
t = Task.Delay(5000);
t.Wait();
}
even though I'm waiting after each task, the UI doesn't get updated - rather it just displays text three directly after method is finished - suggestions ?
P.S: there's also a WPF loader on that page, I've noticed that it doesn't get animated as well - it seems the delay is working but everything on the UI isn't updated
I would suggest for getting the data from REST implementation , you should use the background worker and on the basis of completion of thread or progress changed you need to update the UI thread accordingly.
for getting the better insights on background worker.. kindly use this link
How to use WPF Background Worker
In your case you can use progresschanged event of the backgroundworker..
Please Create some property lets say StatusText with InotifyPropertyChanged Interface implemented and bind (use TwoWay Binding) it with the Text property of the authenticationText control .... and in the progress changed event of the backgroundworker set the value of the StatusText property,., which will automatically updates the UI.
You could try to invoke these results on the UI Thread...
Run your task normally with Task.Run or whatever. Each time you are ready to set some property on UI Thread you should invoke it through the dispatcher..
Task.Run(() =>
{
var _Temp = getSomePropTask();
Thread.Sleep(1000);
App.Current.Dispatcher.Invoke(()=>{
authenticationText.Text = _Temp;
});
});
Thanks to suggestion by Ashok, I did some background reading and have come up with the following solution using Task, async and await - which is simpler to manage than background worker threads:
private void Page_Loaded(object sender, RoutedEventArgs e) {
var wnd = Window.GetWindow(this);
wnd.ContentRendered += Wnd_ContentRendered;
}
private void Wnd_ContentRendered(object sender, EventArgs e) {
GetDataAsync();
}
private async void GetDataAsync() {
authenticationText.Text = "Connecting...";
await Task.Delay(5000);
authenticationText.Text = "Getting Member Details...";
List<MemberServiceModel> memberList = await GetMembersAsync();
// more code for handling response
}
private List<MemberServiceModel> GetMembers() {
//get all members synchronous
var request = new RestRequest("Members/Admin", Method.GET);
var response = _client.Execute<List<MemberServiceModel>>(request);
if (response.ResponseStatus != ResponseStatus.Completed) {
//TODO
_restErrorStatus = response.ResponseStatus.ToString();
_restErrorMessage = response.StatusDescription;
_logger.Error("Error in GetMembers");
_logger.Error("Status:" + _restErrorStatus);
_logger.Error("Description:" + _restErrorMessage);
}
return response.Data; ;
}
private Task<List<MemberServiceModel>> GetMembersAsync() {
//get all members asynchronous
return Task.Run(new Func<List<MemberServiceModel>>(GetMembers));
}

Backgroundworker cancel the worker

I'm facing some troubles when trying to cancel the Backgroundworker.
I've read dozens os similiar topics such as How to stop BackgroundWorker correctly, How to wait correctly until BackgroundWorker completes? but I'm not reaching anywhere.
What's happening is that I have a C# application that uses a PHP WebService to send info to a MySQL database. If the user, for some reason, clicks (in the form) on the "back" or "stop" button, this is the code that is fired:
BgWorkDocuments.CancelAsync();
BgWorkArticles.CancelAsync();
I do understand that the request is Asynchronous, therefore the cancelation might take 1 or 2 seconds, but it should stop..and that doesn't happen at all. Even after clicked "back" (the current form is closed and a new one is opened) the backgroundworker continues to work, because I keep seeing data being inserted into MySQL.
foreach (string[] conn in lines)
{
string connectionString = conn[0];
FbConnection fbConn = new FbConnection(connectionString);
fbConn.Open();
getDocuments(fbConn);
// Checks if one of the backgrounds is currently busy
// If it is, then keep pushing the events until stop.
// Only after everything is completed is when it's allowed to close the connection.
//
// OBS: Might the problem be here?
while (BgWorkDocuments.IsBusy == true || BgWorkArticles.IsBusy == true)
{
Application.DoEvents();
}
fbConn.Close();
}
The above code is needed because I might have multiple databases, that's why I have the loop.
private void getDocuments(FbConnection fbConn)
{
BgWorkDocuments.RunWorkerAsync();
BgWorkDocuments.DoWork += (object _sender, DoWorkEventArgs args) =>
{
DataTable dt = getNewDocuments(fbConn);
for (int i = 0; i <= dt.Rows.Count - 1; i++)
{
// Checks if the user has stopped the background worker
if (BgWorkDocuments.CancellationPending == false)
{
// Continue doing what has to do..
sendDocumentsToMySQL((int)dt.Rows[i]["ID"]);
}
}
// After the previous loop is completed,
// start the new backgroundworker
getArticles(fbConn);
};
}
private void getArticles(FbConnection fbConn)
{
BgWorkArticles.RunWorkerAsync();
BgWorkArticles.DoWork += (object _sender, DoWorkEventArgs args) =>
{
DataTable dt = getNewArticles(fbConn);
for (int i = 0; i <= dt.Rows.Count - 1; i++)
{
// Checks if the user has stopped the background worker
if (BgWorkArticles.CancellationPending == false)
{
// Continue doing what has to do..
sendArticlesToMySQL((int)dt.Rows[i]["ID"]);
}
}
};
}
I agree with the comments expressing surprise that the code even works, due to the apparent ordering problem of the call to RunWorkerAsync() vs when you actually subscribe to the DoWork event. Additionally, your use of DoEvents() is unwarranted and should be removed (as is the case with any use of DoEvents()).
I also note that your workers don't actually exit when you try to cancel them. You just skip the processing, but continue to loop on the rows. Without seeing the rest of the code, it's impossible to know what's going on, but it's possible that after you cancel, the CancellationPending property gets reset to false, allowing the loops to start doing things again.
The lack of a complete code example is a real impediment to understanding the full detail of what's going on.
That said, IMHO this does not seem to be a case where you actually need BackgroundWorker at all, not with the new async/await feature in C#. Given that network I/O is involved, my guess is that each call to sendDocumentsToMySQL() and sendArticlesToMySQL() can be executed individually in the thread pool without too much overhead (or may even be able to be written as async I/O methods…again, lacking detail as to their specific implementation prevents any specific advise in that respect). Given that, your code could probably be rewritten so that it looks more like this:
private CancellationTokenSource _cancelSource;
private void stopButton_Click(object sender, EventArgs e)
{
if (_cancelSource != null)
{
_cancelSource.Cancel();
}
}
private async void startButton_Click(object sender, EventArgs e)
{
using (CancellationTokenSource cancelSource = new CancellationTokenSource)
{
_cancelSource = cancelSource;
try
{
foreach (string[] conn in lines)
{
string connectionString = conn[0];
FbConnection fbConn = new FbConnection(connectionString);
fbConn.Open();
try
{
await getDocuments(fbConn, cancelSource.Token);
await getArticles(fbConn, cancelSource.Token);
}
catch (OperationCanceledException)
{
return;
}
finally
{
fbConn.Close();
}
}
}
finally
{
_cancelSource = null;
}
}
}
private async Task getDocuments(FbConnection fbConn, CancellationToken cancelToken)
{
DataTable dt = await Task.Run(() => getNewDocuments(fbConn));
for (int i = 0; i <= dt.Rows.Count - 1; i++)
{
cancelToken.ThrowIfCancellationRequested();
await Task.Run(() => sendDocumentsToMySQL((int)dt.Rows[i]["ID"]));
}
}
private async Task getArticles(FbConnection fbConn, CancellationToken cancelToken)
{
DataTable dt = await Task.Run(() => getNewArticles(fbConn));
for (int i = 0; i <= dt.Rows.Count - 1; i++)
{
cancelToken.ThrowIfCancellationRequested();
await Task.Run(() => sendArticlesToMySQL((int)dt.Rows[i]["ID"]));
}
}

How do I update the GUI on the parent form when I retrieve the value from a Task?

I think I'm missing something obvious here, but how do I update the GUI when using a task and retrieving the value? (I'm trying to use await/async instead of BackgroundWorker)
On my control the user has clicked a button that will do something that takes time. I want to alert the parent form so it can show some progress:
private void ButtonClicked()
{
var task = Task<bool>.Factory.StartNew(() =>
{
WorkStarted(this, new EventArgs());
Thread.Sleep(5000);
WorkComplete(this, null);
return true;
});
if (task.Result) MessageBox.Show("Success!");//this line causes app to block
}
In my parent form I'm listening to WorkStarted and WorkComplete to update the status bar:
myControl.WorkStarting += (o, args) =>
{
Invoke((MethodInvoker) delegate
{
toolStripProgressBar1.Visible = true;
toolStripStatusLabel1.Text = "Busy";
});
};
Correct me if I'm wrong, but the app is hanging because "Invoke" is waiting for the GUI thread to become available which it won't until my "ButtonClicked()" call is complete. So we have a deadlock.
What's the correct way to approach this?
You're blocking the UI thread Task.Result blocks until the task is completed.
Try this.
private async void ButtonClicked()
{
var task = Task<bool>.Factory.StartNew(() =>
{
WorkStarted(this, new EventArgs());
Thread.Sleep(5000);
WorkComplete(this, null);
return true;
});
await task;//Wait Asynchronously
if (task.Result) MessageBox.Show("Success!");//this line causes app to block
}
You can use Task.Run to execute code on a background thread. The Task-based Asynchronous Pattern specifies a pattern for progress updates, which looks like this:
private async void ButtonClicked()
{
var progress = new Progress<int>(update =>
{
// Apply "update" to the UI
});
var result = await Task.Run(() => DoWork(progress));
if (result) MessageBox.Show("Success!");
}
private static bool DoWork(IProgress<int> progress)
{
for (int i = 0; i != 5; ++i)
{
if (progress != null)
progress.Report(i);
Thread.Sleep(1000);
}
return true;
}
If you are targeting .NET 4.0, then you can use Microsoft.Bcl.Async; in that case, you would have to use TaskEx.Run instead of Task.Run. I explain on my blog why you shouldn't use Task.Factory.StartNew.

Categories