Controlling the progress bar with the backgroundworker made my project difficult after a certain place. In this case, I decided to move on async structure, I built the architecture on the async structure. But this time I did not know how to control the progress bar under the async structure.
private async void button3_Click(object sender, EventArgs e)
{
progressBar1.Value = 1;
int value = 1;
await ProgressBarControl(value);
await Convert();
}
public Task ProgressBarControl(int e)
{
return Task.Run(() =>
{
var progress = new Progress<int>(percent =>
{
progressBar1.Value = percent;
});
});
}
But it is not working. I used backgroundworker. I was asking this question. But you guys suggested to me backgroundworker. But backgroundworker, after a while have a error and system is not answered to me?
The question is unlcear. BackgroundWorker is osbsolete since 2012 anyway.
If you want to report progress from any background thread, not just threads created with Task.Run, create a Progress<T> in the UI thread and pass it only as an IProgress<T> to the background thread or task, eg :
private async void button3_Click(object sender, EventArgs e)
{
var progress = new Progress<int>(percent =>
{
progressBar1.Value = percent;
});
progressBar1.Value = 1;
int value = 100;
await DoSomeWork(value,progress);
}
public Task DoSomeWork(int iterations,IProgress<int> progress)
{
for(int i=0;i<iterations;i++)
{
await Task.Run(()=>{
DoSomethingReallySlow(i);
progress.Report(i*100/iterations));
});
}
}
Check Async in 4.5: Enabling Progress and Cancellation in Async APIs for an example of both progress reporting and cancellation
Related
I have this method that i need to pause for some period of time.
This is what I tried:
private async void btnStart_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() =>
{
this.Dispatcher.Invoke((Action)(() =>
{
NewGame();
// Game Loop
for (int i = 0; i < 35; i++)
{
int index = _random.Next(0, _numbersInGame.Count);
int number = _numbersInGame.ToArray()[index];
lblCurrentNumber.Content = number.ToString();
Task.Delay(1000).Wait(); // I want to add pause here
(this.FindName($"lblNum{i + 1}") as Label).Content = number.ToString();
lblCurrentNumber.Content = "";
_numbersInGame.RemoveAt(index);
}
}));
});
}
Any help is welcome.
Thanks!
You don't need to offload this to a task (with Task.Run)
Since you aren't offloading, you don't need to marshal back to the UI thread with Invoke.
Given the above, you can idiomatically await a Task.Delay without blocking the UI
Example
private async void btnStart_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 35; i++)
{
NewGame();
int index = _random.Next(0, _numbersInGame.Count);
int number = _numbersInGame.ToArray()[index];
lblCurrentNumber.Content = number.ToString();
await Task.Delay(100);
(this.FindName($"lblNum{i + 1}") as Label).Content = number.ToString();
lblCurrentNumber.Content = "";
_numbersInGame.RemoveAt(index);
}
}
Using the async and await pattern like this allows you to safely update the UI as the continuation will be posted back to the UI context. It also allows the Message Pump / Dispatcher to continue unabated until the awaited delay has completed.
Lastly, you might want to protect this method from double clicks with a flag or similar (since the ui is free to reenter)
I have used delegates in the past to update the current UI (for example a textbox) with a value that is being processed in my thread. I was under the assumption that async and await took care of this for you.
When I run the code below -when the button is pressed start a new task that will count up to 100 every 100ms. The count should be displayed on the UI, but I get a targetInvocationException instead:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
textBox1.Text = "Press button";
}
private async void button1_Click(object sender, EventArgs e)
{
await Task1();
}
private Task Task1()
{
return Task.Factory.StartNew(() => CountMethod());
}
private void CountMethod()
{
for (int i = 0; i < 100; i++)
{
Task.Delay(100).Wait();
//Console.WriteLine(i.ToString());
textBox1.Text = i.ToString();
}
}
}
The problem is that you are using a thread-pool thread to access the UI. You are doing this by using the Task.Factory.StartNew method.
Here is how you can fix it:
private async Task Task1()
{
for (int i = 0; i < 100; i++)
{
await Task.Delay(100);
textBox1.Text = i.ToString();
}
}
await will capture the current SynchronizationContext so that after the asynchronous wait is complete, the code that follows executes on the UI thread. This is true because await was called by the UI thread (in the case of my example).
In your current code, await is called on a thread-pool thread. In this case the current SynchronizationContext is null and therefore the code that follows the asynchronous wait will be executed also on a thread-pool thread.
I have a loop that I would like to stop using a button.
Edited for better understanding:
I do realize that you cannot stop a button while a loop was running since it will not work as long as that current UI is running. What I'm really asking for is the most efficient way of creating a thread or using BGWorker to stop this. I have seen some methods, but most of them are for Java and not C#.
What I would like to do is:
private void start_Click(object sender, EventArgs e)
{
for(int i = 0; i < nums; i++)
{
doSomething();
}
}
private void stop_Click(object sender, EventArgs e)
{
stops start_Click()
}
You can't do that. For starters, the for loop is running synchronously on the UI thread, which means you won't even be able to click the "Stop" button.
Hence, you need to move the operations of the for loop onto another thread, which means you likely won't be using a for loop at all. You need to think about how the code inside actually needs to be executed, then based on how you are doing the processing, you can implement the "Stop" button.
A very simple way to do this would be to just:
new Thread(() =>
{
int i = 0;
while (!stop && i < num)
{
doSomething();
i++;
}
}).Start();
And set stop to stop the processing loop. In a more realistic scenario, you could queue up functions that you want to process, then stop dequeuing via a similar method. Unfortunately, its hard to reccommend a setup without knowing more details.
Any solution based on your code will also have the problem of the current doSomething() completing execution (which could take a while). Again, without more info, its hard to say what the best approach to fixing that is.
To keep your UI responsive to be able to cancel the running operation you can use a backgrounworker.
The backgroundworker does the work in an other thread while keeping your UI responsive:
private readonly BackgroundWorker _backgroundWorker;
public Form1()
{
InitializeComponent();
_backgroundWorker = new BackgroundWorker
{
WorkerSupportsCancellation = true
};
_backgroundWorker.DoWork += backgroundWorker_DoWork;
_backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
Disposed += Form1_Disposed;
}
private void Form1_Disposed(object sender, EventArgs e)
{
_backgroundWorker.Dispose();
}
private void StartLoop()
{
if ( !_backgroundWorker.IsBusy )
{
_backgroundWorker.RunWorkerAsync();
}
}
private void StopLoop()
{
_backgroundWorker.CancelAsync();
}
private void backgroundWorker_DoWork( object sender , DoWorkEventArgs e )
{
var backgroundWorker = ( BackgroundWorker ) sender;
for ( var i = 0; i < 100; i++ )
{
if ( backgroundWorker.CancellationPending )
{
e.Cancel = true;
return;
}
// Do Work
}
}
private void backgroundWorker_RunWorkerCompleted( object sender , RunWorkerCompletedEventArgs e )
{
if ( e.Cancelled )
{
// handle cancellation
}
if ( e.Error != null )
{
// handle error
}
// completed without cancellation or exception
}
IMHO, it's likely the best approach here is to convert your work to an asynchronous operation and then use the async/await idiom for the loop. E.g.:
private bool _stopLoop;
private async void start_Click(object sender, EventArgs e)
{
_stopLoop = false;
for(int i = 0; i < nums && !_stopLoop; i++)
{
await Task.Run(() => doSomething());
}
}
private void stop_Click(object sender, EventArgs e)
{
_stopLoop = true;
}
This allows the loop itself to execute in the UI thread where the _stopLoop variable is being managed, but without actually blocking the UI thread (which among other things would prevent the "Stop" button from being clicked).
Unfortunately, you didn't provide details about how doSomething() works. It's possible there's a good way to convert that whole method to be an async method, but I can't comment on that without the actual code.
Note that this approach will only interrupt the loop at a point in between each operation. If you want to be able to interrupt the doSomthing() operation itself, you'll have to provide a mechanism for that. One likely approach would be to use CancellationSource and CancellationToken, which provides a convenient way to express cancellation semantics.
Try using an async/await approach. It's quite easy!
public partial class MyForm : Form
{
public MyForm()
{
InitializeComponent();
}
private CancellationTokenSource _tokenSource;
private async void start_Click(object sender, EventArgs e)
{
if (_tokenSource != null)
return;
_tokenSource = new CancellationTokenSource();
var ct = _tokenSource.Token;
await Task.Factory.StartNew(() =>
{
for (; ; )
{
if (ct.IsCancellationRequested)
break;
doSomething();
}
}, ct);
_tokenSource = null;
}
private int _labelCounter;
private void doSomething()
{
// do something
Invoke((Action)(() =>
{
myLabel.Text = (++_labelCounter).ToString();
}));
}
private void stop_Click(object sender, EventArgs e)
{
if (_tokenSource == null)
return;
_tokenSource.Cancel();
}
}
try this :
bool stop=false;
private void start_Click(object sender, EventArgs e)
{
for(int i = 0; i < nums&& !bool; i++)
{
doSomething();
}
}
and in the click event
set
stop=true;
The usual routine, progress bar, user clicks the button, process runs, progress bar is updated. Classic.
However I would like to run the process the moment entire app starts, so I run it in Loaded handler for Window (the only window in my app).
And now, the progress bar is not updated. How to overcome this problem?
Tester for updating progress bar.
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(100);
Progress.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Render, new Action(delegate() { ++Progress.Value; }));
}
Because you put all the work on the UI thread, which is used to update the Progressbar, so the Progressbar is not updated until the work is done. Put the work on a background thread, and update the Progressbar from the background thread using Dispatcher.BeginInvoke.
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(100);
Progress.Dispatcher.BeginInvoke(new Action(delegate() { ++Progress.Value; }), null);
}
});
}
If you are using .NET 2.0 (not using TPL), you can use ThreadPool,
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
ThreadPool.QueueUserWorkItem( (o) =>
{
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(100);
Progress.Dispatcher.BeginInvoke(new Action(delegate() { ++Progress.Value; }), null);
}
});
}
Or using await-async (.NET 4.5), see, you can call DoWork() from any method that marked as async.
async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
await DoWork();
}
async Task DoWork()
{
await Task.Run(async () =>
{
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(100);
await Progress.Dispatcher.BeginInvoke(new Action(delegate() { ++Progress.Value; }), null);
}
});
}
I am using Task class in my app. This is NOT WPF application! The question is are there any possibilities of calling function from Task body on UI thread, like this:
var task = new Task(() => DoSmth(1));
task.Start();
public void DoSmth(int arg)
{
//smth
CallNotifFuncOnUIThread(() => Notify(1));
//smth ELSE
CallNotifFuncOnUIThread(() => Notify(2));//notify AGAIN
//smth ELSE
}
public void Notify(int arg)
{
progressBar1.Value = arg;
}
Or maybe there are other solutions of this problem? I know about BackgroundWorker class, but what about Tasks?
You can always call other methods inside your DoSth()
Dispatcher.Invoke(...);
Dispatcher.BeginInvoke(...);
You can also user Task.ContinueWith(...) to do sth after the task is finished processing ...
If you have a task you can start it on the gui thread by providing the correct scheduler:
Task.Factory.StartNew(() => DoSomethingOnGUI(), TaskScheduler.FromCurrentSynchronizationContext());
UI is usually STA see: http://msdn.microsoft.com/en-us/library/windows/desktop/ms680112(v=vs.85).aspx
so in order to do something from none UI thread in the UI you need to inject somehow the msg into the thread
see for example htis winform example:
http://weblogs.asp.net/justin_rogers/articles/126345.aspx
watever UI you are using you will need a similar system.
With windows Form and progressBar1 component on ityou can use TPL IProgress interface for Task.
private void Form1_Load(object sender, EventArgs e)
{
Progress<int> progress = new Progress<int>();
var task = Alg(progress);
progress.ProgressChanged += (s, i) => { UpdateProgress(i); };
task.Start();
}
public void Notify(int arg)
{
progressBar1.Value = arg;
}
public static Task Alg(IProgress<int> progress)
{
Task t = new Task
(
() =>
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100);
((IProgress<int>)progress).Report(i);
}
}
);
return t;
}