I have on winform with a textbox and on textchanged executes a background thread:
private void txtFathersLast_TextChanged(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(_ => WaitWhileUserTyping());
}
private void WaitWhileUserTyping()
{
var keepWaiting = true;
while (keepWaiting)
{
_keyPressed = false;
Thread.Sleep(TypingDelay);
keepWaiting = _keyPressed;
}
Invoke((MethodInvoker)(ExecuteSearch));
_waiting = false;
}
private void ExecuteSearch()
{
Thread.Sleep(200);
Task.Factory.StartNew(() =>
{
using (DataReference.SearchWCF search = new DataReference.SearchWCF())
{
_similaritySearchResults = search.SearchPersonBySimilarity(txtFathersLast.Text, txtMothersLast.Text, txtName.Text, DateTime.Now, 10);
}
}).ContinueWith(t=>{
if (this.InvokeRequired)
{
this.BeginInvoke(new Action(() =>
{
if (_similaritySearchResults != null && _similaritySearchResults.Tables["data"].Rows.Count > 0)
{
DataTable dt = _similaritySearchResults.Tables["data"];
Infragistics.Win.Misc.UltraTile newTile = null;
for (int index = 0; index < dt.Rows.Count; index++)
{
newTile = new Infragistics.Win.Misc.UltraTile("Person X");
newTile.Control = new CustomControls.Controls.PersonResult("123", "123", index + 150);
newTile.Tag = new Guid("90D27721-7315-4B86-9CFD-4F7D02921E9A");
newTile.DoubleClick += TileDoubleClick;
tilePanel.Tiles.Add(newTile);
}
}
}));
}
else
{
if (_similaritySearchResults != null && _similaritySearchResults.Tables["data"].Rows.Count > 0)
{
DataTable dt = _similaritySearchResults.Tables["data"];
Infragistics.Win.Misc.UltraTile newTile = null;
for (int index = 0; index < dt.Rows.Count; index++)
{
newTile = new Infragistics.Win.Misc.UltraTile("Person X");
newTile.Control = new CustomControls.Controls.PersonResult("123", "123", index + 150);
newTile.Tag = new Guid("90D27721-7315-4B86-9CFD-4F7D02921E9A");
newTile.DoubleClick += TileDoubleClick;
tilePanel.Tiles.Add(newTile);
}
}
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}
This is working fine, the application goes to a database then get results and update the UI, adding tiles to a control depending of the number of records returned by database.
Now, the problem comes when I try to add another background thread into my custom control:
new CustomControls.Controls.PersonResult("123", "123", index + 150);
The code for the control is:
protected override void InitLayout()
{
// if I comment this then everything works fine
// but if I leave this, then the UI freezes!!
GetPictureAsync();
base.InitLayout();
}
/// <summary>
///
/// </summary>
private void GetPictureAsync()
{
// This line needs to happen on the UI thread...
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
Random sleep = new Random();
System.Threading.Thread.Sleep(sleep.Next(1000,3000));
if (this.pbPhoto.InvokeRequired)
{
this.pbPhoto.Invoke(new Action(() =>
{
this.Load(#"E:\Photos\" + PhotoId.ToString() + ".jpg");
//this.pbPhoto.Image = Utility.Common.GetResourceImage("woman_sample.jpg");
}));
}
else
{
this.Load(#"E:\Photos\" + PhotoId.ToString() + ".jpg");
//this.pbPhoto.Image = Utility.Common.GetResourceImage("woman_sample.jpg");
}
}, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
}
So the problem seems to be that I first execute a thread for looking when to start search, then inside that thread I run another thread in order to get data from database, and then each control updated in the UI will run another thread to get a picture and update a picturebox.
Anyone knows how to solve this? or a way to work around this?
When you call
new CustomControls.Controls.PersonResult("123", "123", index + 150)
Is "123" a literal string, or are they being read from UI controls. For example,
new CustomControls.Controls.PersonResult(txtFathersName.Text", txtMothersName.Text, index + 150)
i cant test it right now, but isnt accessing the Text property not allowed from a thread other than the one that created the control?
I think the problem lies in you forcing the Task in GetPictureAsync to execute on UI thread and then you are calling Thread.Sleep(). This update UI in Task using TaskScheduler.FromCurrentSynchronizationContext question tackles the same problem as you are having. I would rewrite your code as:
private void async GetPictureAsync()
{
Random sleep = new Random();
await TaskEx.Delay(sleep.Next(1000,3000));
this.Load(#"E:\Photos\" + PhotoId.ToString() + ".jpg");
}
Related
I have some bugs that need fixing, one of them involves an out of memory error.
Does anyone know how to do this properly? Thanks, I don't want it to be too messy, or too complicated. I just want to treat a new image as a buffer to render another image to (because of positional changes), and do it via a background thread. Not the UI thread (Too slow likely).
I get out of memory errors, and such. Also not able to access members of Form1 from within the thread function (images and the like throw access errors such as "Object already in use")
Here is my code:
System.Threading.Thread t;
public Image b;
public Bitmap c;
public Bitmap d;
public Bitmap e;
public Bitmap bg;
public Bitmap spr;
int spritex = 0;
int spritey = 0;
int spritedir = 1;
public Form1()
{
InitializeComponent();
Text = "Escape The Hypno Mansion!!".ToString();
t = new System.Threading.Thread(DoThisAllTheTime);
t.Start();
textBox1.Text = "Press Begin button to start!";
pictureBox1.Image = Image.FromFile(#"Images\introgirl.jpg");
b = new Bitmap(#"Images\introgirl.jpg");
c = new Bitmap(#"Images\sprite.png");
var graphics = Graphics.FromImage(b);
Pen blackpen = new Pen(Color.Black, 3);
graphics.DrawLine(blackpen, 0, 0, 100, 100);
graphics.DrawImage(c, new Point(500, 500));
pictureBox1.Image = b;
//pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
}
public void DoThisAllTheTime()
{
while (true)
{
Point p = new Point(spritex, spritey);
bg = new Bitmap(#"Images\test.bmp");
spr = new Bitmap(#"Images\sprite.png");
using (var graphics = Graphics.FromImage(bg))
{
graphics.DrawImage(spr, p);
}
if (pictureBox1.Image != null)
{
pictureBox1.Image.Dispose();
}
pictureBox1.Image = bg;
pictureBox1.Invalidate();
if (spritedir == 1) { spritex += 5; }
if (spritedir == 2) { spritex -= 5; }
if (spritex < 0) { spritex = 0; spritedir = 1; }
if (spritex > 700) { spritex = 700; spritedir = 2; }
}
}
The reason you can't change the image in your picturebox is because the thread that created the image is not the thread that created the picturebox.
In a debugger you can check this by asking the picturebox for InvokeRequired (function Control.IsInvokeRequired) just before changing the function.
So let's rewrite your function and show that modern classes Like Task are much easier to use the your thread.
I'll start your task when the form is loading, and try to stop it when the form is closing.
private Task myTask = null;
private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private void OnFormLoading(object sender, EventArgs e)
{
// Start the task
this.myTask = Task.Run( () => DoMyWork(this.cancellationTokenSource.Token));
}
private void OnFormClosing(object sender, FormClosingEventArgs e)
{
// if the Task is still running, ask it to stop itself:
if (myTask != null && !myTask.IsCompleted)
{ // ask the task to stop and wait until it is completed:
this.cancellationTokenSource.Cancel();
// all Tokens extractes from this source will get state CancellationRequested
// wait maximum 5 seconds until the task is completed:
this.UseWaitCursor = true;
this.myTask.Wait(TimeSpan.FromSeconds(5));
this.UseWaitCursor = false;
// cancel closing if the task is still not completed
e.Cancel = !this.myTask.Completed;
}
}
Now the function DoMyWork:
private void DoMyWork(CancellationToken cancellationToken)
{
// Do the same as in your DoThisAllTheTime
// except that you regularly check cancellationToken.IsCancelRequested:
while(!cancellationToken.IsCancelRequested)
{
// calculate the image to display
var imageToDisplay = ...
this.DisplayImage(imageToDisplay);
}
}
void DisplayImage(Image imageToDisplay)
{
if (this.pictureBox1.InvokeRequired)
{
this.Invoke(new MethodInvoker( () => this.DisplayImage(imageToDisplay)));
}
else
{
this.PictureBox1.Image = imageToDisplay;
}
}
See:
How to cancel a Task and its children
Use InvokeRequired with lambda expression
Dispose every disposable instances before the loop ends. Your memory leak is related with disposable items not being cleaned from memory, so you'll eventually run out of memory in your infinite loop.
At the very least, you'll want to dispose both bitmaps at the end of the loop:
bg = new Bitmap(#"Images\test.bmp");
spr = new Bitmap(#"Images\sprite.png");
I have a long running WCF service and a client that consumes it via WPF. Am using a Progress Bar to notify the client of the percentage completion for a particular process (a method in WCF: I need to be able to display the percentage based on the looping counter in the service)
I have used Background Worker to display progress percentage but it does not display the progress correctly. (displays just 0 and 100 not the in between values) Everything works fine in DEBUG mode but not in RELEASE mode! (Progress bar is updated sequentially in DEBUG mode)
I tried using callbacks/wsDualHttpBinding but have some difficulty in getting this incorporated for all clients. So, had to drop this option.
working on async/await
I have googled quite a few links but nothing helps with my problem.
Please guide me on how to get the current/running value from a method that is not complete yet from a WCF service so I could populate the progress bar percentage based on this value. (in between values)
P.S: WCF service uses wsHttpBinding
sample code below:
public Progress()
{
// Start the BackgroundWorker.
myBGWorker.WorkerReportsProgress = true;
myBGWorker.WorkerSupportsCancellation = false;
myBGWorker.DoWork += myBGWorker_DoWork;
myBGWorker.ProgressChanged += myBGWorker_ProgressChanged;
}
public void ShowProgress()
{
myBGWorker.RunWorkerAsync();
}
private void myBGWorker_DoWork(object sender, DoWorkEventArgs e)
{
// fetches a static value from the service
string value = _client.Progress();
int p=0;
for (int i = 1; i <= 100; i++)
{
// Report progress.
p = Convert.ToInt32(_client.Progress());
_logger.Debug("Progress5:" + p.ToString());
myBGWorker.ReportProgress(p, i);
}
}
private void myBGWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.Dispatcher.BeginInvoke(new Action(delegate
{
progressBar1.Value = e.ProgressPercentage;
}), DispatcherPriority.ContextIdle);
}
Just to understand how it could be implemented. This is part of my working code.
.xaml file:
<ProgressBar x:Name="ProgressBarCompare" HorizontalAlignment="Left" Height="20" Margin="10,157,0,0" VerticalAlignment="Top" Width="321"/>
Function with process:
private async void btnCompare_Click(object sender, RoutedEventArgs e)
{
ProgressBarCompare.Value = 0;
lblCompare.Content = "";
List<string> list1= (List<string>)Application.Current.Properties["list1"];
List<string> list2= (List<string>)Application.Current.Properties["list2"];
List<Result> output = new List<Result>();
List<Result> passed = new List<Result>();
int topCount = emailList.Count;
int currentItem = 0;
int topBound = topCount - 1;
while (currentItem < topCount)
{
var hash = await CheckOperation(list1[currentItem]); // this line perform progress bar to be filled
var result = list2.Contains(hash);
//some operations
if (Convert.ToInt32(Math.Ceiling(100d * currentItem / topBound)) < 51)
{
Style style = this.FindResource("LabelTemplateNotFilled") as Style;
lblCompare.Style = style;
}
else
{
Style style = this.FindResource("LabelTemplateFilled") as Style;
lblCompare.Style = style;
}
ProgressBarCompare.Value = Convert.ToInt32(Math.Ceiling(100d * currentItem / topBound));
lblCompare.Content = Convert.ToInt32(Math.Ceiling(100d * currentItem / topBound)) + "%";
currentItem++;
}
lblCompare.Content = "COMPLETE";
}
and core function to that:
private async Task<string> CheckOperation(string input)
{
var result = "";
await Task.Run(() =>
{
//perform some code
});
return result;
}
I was trying to update status on UI for a Long Running Operating. I've created a demo form based application, task it have multiple rows, each row is having days and default values in each column of datagrid is 0, once computation file computes one iteration for one day it will update UI and set 1 for that day.
I am using threading, delegates and events to implement this and it is working as expected if I put Thread.Sleep(100) between two event calls. If I put "Thread.Sleep(100)" inside last nested for loop then it updates UI as expected but as soon as I remove it and run loop without sleep, then it skips some of the columns on UI and directly update last few/random columns, as you can see in attached image link(Image of output of my code without thread sleep) only last column is getting updated.
If I am not mistaken all the events are getting fired in sequence then they should update UI in sequence too but it's not happening and I don't know why. I don't want to do this Sleep thing because I have around 14 calls in actual application for UI status update and it will run under a loop so if It put sleep(100) then it will cost me a lot, is there any way to do it without SLEEP?
Image of output of my code without thread sleep
public class Class1 : IGenerate
{
public event MessageEventHandler OnMessageSending;
public void LongOperationMethod(BindingList<Status> _statusData)
{
if (OnMessageSending != null)
{
MessageEventArgs me = new MessageEventArgs();
/// Loop for 2-3 Weeks
for (; ; ){
/// Loop for 7 day
for (; ; )
{
/// Calculation on everyday
for (int j = 0; j != 1000; ++j)
{
// to do
}
me.weekNo = k;
me.DayNo = i;
OnMessageSending(me);
}
}
me.Message = "Process completed successfully...";
OnMessageSending(me);
}
else
{
throw new ArgumentException("Event hasn`t been rised, so we cannot continue working.");
}
}
}
**UI file:**
<pre><code>
public partial class Form1 : Form
{
BindingList<Status> _statusData = new BindingList<Status>();
delegate void StringParameterDelegate(string value);
Class1 cls = new Class1();
public Form1()
{
InitializeComponent();
labelProgress.Text = "";
}
private void button1_Click_1(object sender, EventArgs e)
{
for (int i = 1; i <= 2; ++i)
{
_statusData.Add(new Status { Week = "Week" + i, Day1 = 0, Day2 = 0, Day3 = 0, Day4 = 0, Day5 = 0, Day6 = 0, Day7 = 0 });
}
dataGridView1.DataSource = _statusData;
}
private void button2_Click(object sender, EventArgs e)
{
Thread t1 = new Thread(() => StartingThread(_statusData));
t1.Start();
}
void StartingThread(BindingList<Status> _statusData)
{
IGenerate generate = new Class1();
generate.OnMessageSending += new MessageEventHandler(generate_OnMessageSending);
generate.LongOperationMethod(_statusData);
}
private void generate_OnMessageSending(MessageEventArgs e)
{
int weekNo = e.weekNo;
int dayNo = e.DayNo;
this.dataGridView1.BeginInvoke(new MethodInvoker(() => dataGridView1.Rows[e.weekNo].Cells[e.DayNo + 1].Value = 1));
this.labelProgress.BeginInvoke(new MethodInvoker(() => this.labelProgress.Text = e.Message));
}
}
</code></pre>
It looks like you are sending the same instance of MessageEventArgs every time, and just updating that one instance on the background thread. This means that your event handler on the UI thread will retrieve the exact same instance of MessageEventArgs that is being updated in the loop! By the time your UI handler gets the MessageEventArgs, its .weekNo and .DayNo properties could well have been modified by the next iteration of the loop, since they are running on separate threads.
To fix this, create a new instance of MessageEventArgs every time you call OnMessageSending().
Relevant snippet:
MessageEventArgs me = new MessageEventArgs();
me.weekNo = k;
me.DayNo = i;
OnMessageSending(me);
So I'm trying to change the text from a WinForms project, from another class than the Form class.
It should work like this:
But instead it does this:
The way I used to do it is pass along the object as a parameter to my other class and from that other class I could change the text. I do the same with the progressbar and it does work there, so it's weird that it works with the progressbar but not the label.
I use this method to change the progressbar:
public void IncreaseProgress(int progBarStepSize, String statusMsg, int currentProject=-1) {
if (currentProject != -1) {
lblStatus.Text = String.Format("Status: {0} | project {1} of {2}",statusMsg,currentProject,ProjectCount);
}
else {
lblStatus.Text = String.Format("Status: {0}",statusMsg);
}
pb.Increment(progBarStepSize);
}
And here is where I use the method:
public void InitialiseFile(List<string> filePaths, int eurojobType)
{
foreach (string sheet in outputSheets) {
switch (sheet) {
case "Summary":
for (int i = 0; i < filePaths.Count; i++) {
var filePath = filePaths[i];
IncreaseProgress(1, "Reading Summary", i);
worksheetIn = excelReader.ReadExcelSummary(filePath);
IncreaseProgress(1, "Writing Summary", i);
excelWriter.WriteExcelSummary(worksheetIn);
}
break;
case "Monthly_Cat1":
for (int i = 0; i < filePaths.Count; i++) {
var filePath = filePaths[i];
IncreaseProgress(1, "Reading Monthly", i);
worksheetIn = excelReader.ReadExcelMonthly(filePath);
IncreaseProgress(1, "Writing Monthly", i);
excelWriter.WriteExcelMonthly(worksheetIn);
}
break;
}
}
IncreaseProgress(1, "Completed!");
}
Now I know this code works because the progressbar increments. And it should jump in the first if-loop because i gets passed as a parameter, which is never -1.
//manager class
private Label lblStatus;
private ProgressBar pb;
public Manager(ProgressBar pb, Label lbl){
this.pb = pb;
lblStatus = lbl;
}
//Form class
Manager mgr = new Manager(progressBar1, lblStatus, projectFilePaths.Count, outputSheets.ToArray(), exportPath);
mgr.InitialiseFile(projectFilePaths, eurjobType);
You can call lblStatus.Refresh(); to force the control to be redrawn, after setting its Text.
But consider Slaks comment:
Don't do blocking work on the UI thread
You can consider using BackgroundWorker or Task.Run or async/await pattern instead.
As an example:
private async void button1_Click(object sender, EventArgs e)
{
await Task.Run(() =>
{
for (int i = 0; i < 10000; i++)
{
this.Invoke(new Action(() =>
{
label1.Text = i.ToString();
label1.Refresh();
}));
}
});
}
This way the numbers increase, the label refreshes and shows the new value, while the UI is responsive and for example you can move your form or click on other button.
You should put your UI related codes in an action fired by Invoke to prevent receiving cross thread operation exception.
I recently encountered a situation for which I have no explanation so far
Trying to work on cross-threading operations with winform I wrote a short piece of code to test solutions faster.
I have a form including a progress bar, a a DataGridView and a Button
public MainForm()
{
this._progressBar = new ProgressBar { Dock = DockStyle.Top, };
this._button = new Button { Dock = DockStyle.Bottom, Text = #"&GO!" };
this._dataGridView = new DataGridView {Dock = DockStyle.Fill,};
this.Controls.Add(this.ProgressBar);
this.Controls.Add(this.Button);
this.Controls.Add(this.DataGridView);
this.WindowsFormsSynchronizationContext = WindowsFormsSynchronizationContext.Current as WindowsFormsSynchronizationContext;
this._records = new SpecialBindingList<Record>();
//this._records = new SpecialBindingList<Record>();
this.DataGridView.DataSource = this.Records;
this.Button.Click += this.button_Click;
}
The button has an event OnClick
private void button_Click(object sender, EventArgs e)
{
var dispatcherUI = Dispatcher.CurrentDispatcher;
Action action = () =>
{
while (true)
{
Task.Factory.StartNew(() =>
{
var value = (this.ProgressBar.Value == this.ProgressBar.Maximum)
? 0
: this.ProgressBar.Value + 1;
var record = new Record
{
A = DateTime.Now.Second.ToString(),
B = DateTime.Now.Millisecond.ToString()
};
if (Thread.CurrentThread != dispatcherUI.Thread)
{
dispatcherUI.BeginInvoke(new Action(() => { ProgressBar.Value = value; }), null);
}
this.WindowsFormsSynchronizationContext.Send((state) => this.Records.Add(record), null);
Thread.Sleep(100);
});
}
};
var task = new Task(action);
task.Start();
this.Button.Enabled = false;
}
It creates a task, which is filling the progress bar and adding new lines to the grid, then start it and disable the button.
The problem is, is I start the code like this the action (state) => this.Records.Add(record) would always throw an InvalidOperationException when adding a record to my Bindinglist.
However I realized that if I skip the this.Button.Enabled = false; line my code would execute without any problem !
This seems a little odd to me, so I was wondering why modifying a button property would creates an exception on an apparently unrelated operation