bind log Asynchronous in winForm c# - c#

I tried to add something to log inside WinForm while doind something
private async Task SaveChunk(DataChunkSaver chunk)
{
int i = 0;
int step = 10;
while (chunk.saveChunk(i, step))
{
i += step;
AddLog(chunk.Log);
}
}
where:
private async Task AddLog(string text)
{
LogBulider.AppendLine(text);
LogBox.Text = LogBulider.ToString();
}
AndLogBulider is a simple global StringBulider.
The problem is when I fire button with SaveChunk task my form freezes, so I can see the LogBox after everything is done and I wanned it to bisplayed after each step of chunk.saveChunk.
I tried to fire them by few methods, but I can't handle it
What Am I doing wrong?
private async void button2_Click(object sender, EventArgs e)
{
await Task.Factory.StartNew(() => SaveChunk(chunk));
Task T = SaveChunk(chunk);
// none of these works, I also tried few other
//ways to do it, but none prevents my winForm from freezing
}

I tried to modify your code using a Progress<string>:
private async void button2_Click(object sender, EventArgs e)
{
var progress = new Progress<string>(msg =>
{
LogBulider.AppendLine(msg);
LogBox.Text = LogBulider.ToString();
});
await Task.Run(() => SaveChunk(chunk, progress));
}
and
private async Task SaveChunk(DataChunkSaver chunk, IProgress<string> progress)
{
int i = 0;
int step = 10;
while (chunk.saveChunk(i, step))
{
i += step;
progress?.Report(chunk.Log); // Always use progress as if it could be null!
}
}

Related

C# - Break Loop from another button

In my program i'm starting for loop using button, I want to break this for loop using another button.
For example:
private void button1_Click(object sender, EventArgs e)
{
for( int i = 0; i < var; i++)
{
//doing something
}
}
And using second button break loop,
private void button2_Click(object sender, EventArgs e)
{
//breaking loop;
}
Need help :)
Set a flag in button2_Click() method and check it in the button1_Click()'s loop.
In order to process Windows events and allow button2_Click() handle to run while iterating, add Application.DoEvents() in your loop:
bool breakLoop = false;
private void button1_Click(object sender, EventArgs e)
{
breakLoop = false;
for( int i = 0; i < var && !breakLoop; i++)
{
//doing something
Application.DoEvents();
}
}
private void button2_Click(object sender, EventArgs e)
{
breakLoop = true;
}
You cannot do that, because the loop in button1_Click event handler will be holding the UI thread. Your user interface will not respond to any event, showing hourglass icon, until the loop is over. This means that button2_Click cannot be entered until button1_Click has completed.
You need to replace the long-running loop from the event handler with something that runs outside the UI thread. For example, you can use Tasks, which can be cancelled using CancellationToken (related Q&A).
Arguably it would be better to use threads and cancellation tokens in some form, rather than the Application.DoEvents(). Something like this:
private CancellationTokenSource loopCanceller = new CancellationTokenSource();
private void button1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
try
{
for (int i = 0; i < 100; i++)
{
this.loopCanceller.Token.ThrowIfCancellationRequested(); // exit, if cancelled
// simulating half a second of work
Thread.Sleep(500);
// UI update, Invoke needed because we are in another thread
Invoke((Action)(() => this.Text = "Iteration " + i));
}
}
catch (OperationCanceledException ex)
{
loopCanceller = new CancellationTokenSource(); // resetting the canceller
Invoke((Action)(() => this.Text = "Thread cancelled"));
}
}, loopCanceller.Token);
}
private void button2_Click(object sender, EventArgs e)
{
loopCanceller.Cancel();
}

Pausing a method until a button click

How can i pause a method execution or the current iteration until the user press next button for example?
I want an efficient way because i can't divide the method into other methods and I don't want to use Thread.Sleep() because it freezes the GUI.
public void Calc(int x)
{
while(x < 4)
{
//My Work
textbox1.Text += "Press next to continue";
//Need to pause the iteration until taking a signal from a button
}
}
void button1(...)
{
Calc(1);
}
Use SemaphoreSlim
You can run both work and button code on UI thread, thanks to async/await scheduling. And you can reuse same semaphore instance to make multiple signals.
//not signaled semaphore with maximum of 1 signal
SemaphoreSlim _workSignal = new SemaphoreSlim(0,1);
Your work code:
async void DoWork()
{
//Do Something
//this tries to decrease signal count and if signal count is 0,
//waits until it will have some signals, then "takes"
//one signal to go through.
//After this line the semaphore will be in non-signaled state
await _workSignal.WaitAsync();
//Do more
}
Your button handler
void Button_Click(...)
{
_signal.Release();//increases signal count, allowing your work code to go through
}
I think a simple ManualResetEvent along with async method may help you:
public partial class Form1 : Form
{
private ManualResetEvent _calcEvent;
private delegate void ChangeTextMethod(string text);
private ChangeTextMethod _changeTextHandler;
public Form1()
{
InitializeComponent();
_calcEvent = new ManualResetEvent(false);
_changeTextHandler = delegate (string text) {
textbox1.Text += text;
};
}
private void button1_Click(object sender, EventArgs e)
{
Calc(1);
}
public async Task Calc(int x)
{
await Task.Run(() => {
while (x < 4)
{
//My Work
textbox1.Invoke(_changeTextHandler, "Press next to continue");
//Need to pause the iteration until taking a signal from a button
_calcEvent.WaitOne();
x = 4;
}
textbox1.Invoke(_changeTextHandler, "... continued");
});
}
private void button2_Click(object sender, EventArgs e)
{
_calcEvent.Set();
}
}

Asynchronously update UI

I'm playing with asynchronous operations and my goal right now is very simple. I just want to update a text field at the beginning of a large computation, and the problem I'm facing is that the text field gets updated but only when calculate() returns, even though the method computingMessage() is called immediately:
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
e.Handled = true;
annunciate();
calculate();
}
private void annunciate(){
Thread updateUI = new Thread( new ThreadStart( computingMessage ));
updateUI.Start();
}
private void computingMessage(){
txtVerification.Dispatcher.Invoke((Action)(
() => txtVerification.Text = "Calculating..."
));
}
Please, check Task object.
private void Button_Click(object sender, RoutedEventArgs e)
{
TextBoxOutput.Text = "calculating...";
Task.Factory
.StartNew(() => Calculate())
.ContinueWith(t =>
{
TextBoxOutput.Text = t.Result.ToString(CultureInfo.InvariantCulture);
}, TaskScheduler.FromCurrentSynchronizationContext());
}
private int Calculate()
{
Thread.Sleep(2000); //--similate working....
return Environment.TickCount ^ 43;
}
I hope it helps you.
Although I agree with #dbvega in spirit. I cannot agree with the usage of Task.Factory.StartNew. Nor should you use Dispatcher.Invoke. By default, Task.Factory.CurrentScheduler will be set to the WPF message pump scheduler when running WPF. When running WinForms, there is a WinForm scheduler that is automatically set...
private async void Button_Click(object sender, RoutedEventArgs e)
{
TextBoxOutput.Text = "calculating...";
var result = await Task.Run(Calculate);
TextBoxOutput.Text = result.ToString(CultureInfo.InvariantCulture);
}
private int Calculate()
{
Thread.Sleep(2000); //--similate working....
return Environment.TickCount ^ 43;
}

ReportProgress doesn't call progressChanged with tasks in c#

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int currentProgress=-1;
while (currentProgress<length)
{
currentProgress=Worker.progress;
backgroundWorker1.ReportProgress(currentProgress);
Thread.Sleep(500);
length = Worker.UrlList.Count;
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
int ix = e.ProgressPercentage;
progressBar1.Value = ix;
lblText.Text =ix+" %";
}
I wrote a program to download page sources by reading a file have about 1000 URLs. so I used Tasks to download pages async. here Worker.progress is the currently executed URL amount. though the debuger hits the backgroundWorker1.ReportProgress(currentProgress); it never enter to the backgroundWorker1_ProgressChanged.
private void StartButton_Click(object sender, EventArgs e)
{
t.makeUrlList(inputFile);
backgroundWorker1 = new BackgroundWorker();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.DoWork += backgroundWorker1_DoWork;
backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
backgroundWorker1.RunWorkerAsync();
t.RunTasks();
Application.Exit();
}
background worker initializes when start button clicks...
here is where my tasks are created....
public void RunTasks()
{
if (numOfTasks > UrlList.Count)
numOfTasks=UrlList.Count-1;
Task[] t = new Task[numOfTasks];
int j = 0;
while ( j < UrlList.Count-1)
{
for (int i = 0; (i < t.Count())&&(j<UrlList.Count-1); i++)
{
try
{
if (t[i].IsCompleted || t[i].IsCanceled || t[i].IsFaulted)
{
t[i] = Task.Run(() => FindWIN(j));
j++;
progress = j;
}
}
catch (NullReferenceException ex)
{
t[i] = Task.Run(() => FindWIN(j));
j++;
progress = j;
}
}
}
}
If you want to BackgroundWorker supports updating progress information, the value of WorkerReportsProgress should be set to true . If this property is true , the user code can call ReportProgress for initiating event ProgressChanged .
Background worker initialization:-
backgroundWorker1 = new BackgroundWorker();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.DoWork+=backgroundWorker1_DoWork;
backgroundWorker1.ProgressChanged+=backgroundWorker1_ProgressChanged;
backgroundWorker1.RunWorkerAsync();
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int currentProgress = -1;
decimal length=1000;
while (currentProgress < length)
{
currentProgress = Worker.progress;
backgroundWorker1.ReportProgress(currentProgress);
Thread.Sleep(500);
length = Worker.UrlList.Count;
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) {
int ix = e.ProgressPercentage;
progressBar1.Value = ix;
lblText.Text = ix + " %";
}
See the demo code below. This is mostly untested, and certainly isn't 'production standard', but it should give you a good start!
It uses a ConcurrentQueue to hold the list of URLs to be processed. This is threadsafe, and makes life a lot easier.
It has a configurable number of urls and tasks. It's best not to make 1000 tasks, but instead have a queue of work items, and a smaller pool of Tasks which 'pull items' off the queue until it's empty. This means you can performance test different numbers of Tasks and find the best value for your problem.
It uses Invoke when updating the progress bar - this avoids the cross-thread exception.
No BackgroundWorker - just TaskFactory and Task
public partial class Form1 : Form
{
private const int UrlCount = 1000;
private const int taskCount = 10;
private ConcurrentQueue<string> urlList;
private List<Task> taskList;
public Form1()
{
InitializeComponent();
}
private void ResetQueue()
{
// fake up a number of strings to process
urlList = new ConcurrentQueue<string>(Enumerable.Range(0, UrlCount)
.Select(i => "http://www." + Guid.NewGuid().ToString() + ".com"));
}
private void button1_Click(object sender, EventArgs e)
{
ResetQueue();
var taskFactory = new TaskFactory();
// start a bunch of tasks
taskList = Enumerable.Range(0, taskCount).Select(i => taskFactory.StartNew(() => ProcessUrl()))
.ToList();
}
void ProcessUrl()
{
string current;
// keep grabbing items till the queue is empty
while (urlList.TryDequeue(out current))
{
// run your code
FindWIN(current);
// invoke here to avoid cross thread issues
Invoke((Action)(() => UpdateProgress()));
}
}
void FindWIN(string url)
{
// your code here
// as a demo, sleep a sort-of-random time between 0 and 100 ms
Thread.Sleep(Math.Abs(url.GetHashCode()) % 100);
}
void UpdateProgress()
{
// work out what percentage of the queue is processed
progressBar1.Value = (int)(100 - ((double)urlList.Count * 100.0 / UrlCount));
}
}
You should set WorkerReportsProgress property of your worker to true on initialization stage.

Async Behaviour

I have the following code to update the progress bar in async fashion and i notice
its async behaviour through the call to MessageBox.In this case it works perfectly
but when i give a sleep of 1s(1000) the MessageBox doesnot pops up and the the complete progress bar fills at once.
Kindly tell why this is happening.
private void button1_Click(object sender, EventArgs e)
{
Update_Async async = new Update_Async(Update_Async_method);
progressBar1.BeginInvoke(async,10);
MessageBox.Show("Updation In Progress");
}
public void Update_Async_method(int a)
{
this.progressBar1.Maximum = a;
for (int i = 1; i <= a; i++)
{
progressBar1.Value = a;
Thread.Sleep(10);
//Thread.Sleep(1000);
}
}
Try Update_Async.BeginInvoke(async, 10) instead if you want the delegate to run asynchrnously but, you'll have to cross thread checking on the update to the progress bar.
In response to your comment, very similar to what you are doing already,
void UpdatingFunction(int value)
{
if (this.progressBar.InvokeRequired)
{
this.progressBar.BeginInvoke(UpdatingFunction, value);
return;
}
// Invoke not required, work on progressbar.
}
This also explains what the Invoke methods on controls are for.
Delegate.BeginInvoke will run a method in a thread once and then dispose it. It is a poor choice if you want to repeatedly do some work in a thread and return intermediate results. If that is what you want, you should use BackgroundWorker. Highly abbreviated snippet:
BackgroundWorker bw;
YourFormConstructor()
{
...
bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += BackgroundCalculations;
bw.ProgressChanged += ShowBackgroundProgress;
}
private void button1_Click(object sender, EventArgs e)
{
bw.RunWorkerAsync(10);
}
void ShowBackgroundProgress(object sender, ProgressChangedEventArgs e)
{
this.progressBar.Value = e.ProgressPercentage;
}
static void BackgroundCalculations(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
int max = (int)e.Argument;
for (int i = 0; i < max; i++)
{
bw.ReportProgress(i * 100 / max);
Thread.Sleep(10);
}
bw.ReportProgress(100);
}
}

Categories

Resources