I have a code that fetches tweets from a specific Twitter account using Tweetsharp library, creates instance of a custom UserControl and post tweet text to that UserControl then add it to a StackPanel.
However, I have to get a lot of tweets and it seems that the application would freeze while adding user controls to the StackPanel. I tried using BackgroundWorker, but I wasn't lucky until now.
My code :
private readonly BackgroundWorker worker = new BackgroundWorker();
// This ( UserControl ) is used in the MainWindow.xaml
private void UserControl_Loaded_1(object sender, RoutedEventArgs e)
{
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.RunWorkerAsync();
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
int usrID;
var service = new TwitterService(ConsumerKey, ConsumerSecret);
service.AuthenticateWith(AccessToken, AccessTokenSecret);
ListTweetsOnUserTimelineOptions options = new ListTweetsOnUserTimelineOptions();
options.UserId = usrID;
options.IncludeRts = true;
options.Count = 10;
twitterStatuses = service.ListTweetsOnUserTimeline(options);
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
try
{
foreach (var item in twitterStatuses)
{
TweetViewer tweetViewer = new TweetViewer(); // A UserControl within another UserControl
tweetViewer.Tweet = item.Text;
tweetViewer.Username = "#stackoverflow";
tweetViewer.RealName = "Stack Overflow"
tweetViewer.Avatar = ImageSourcer(item.Author.ProfileImageUrl);
stackPanel.Children.Add(tweetViewer);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
What I want to do now is to solve the problem of not being able to perform the code contained in worker_RunWorkerCompleted within a BackgroundWorker but every time I try to perform it using a BackgroundWorker it fails & gives me errors like :
The calling thread must be STA, because many UI components require this.
I tried also using a STA System.Threading.Thread instead of the BackgroundWorker but without luck!
What am I missing ? I'm really new to WPF and I may be ignoring something important.
You get this exception because your background worker uses a new thread, and this thread is different than the main UI thread.
To simplify the error message says that you cannot change your UI element from another thread, they are independant.
This answer will solve your problem.
I also found this answer from #Marc Gravell
///...blah blah updating files
string newText = "abc"; // running on worker thread
this.Invoke((MethodInvoker)delegate {
someLabel.Text = newText; // runs on UI thread
});
///...blah blah more updating files
Related
I have a background worker with a long running task. The task goes through a list of files and I want to update the user with which file we are on. I have a tool strip that has a label named panel1.text. The progress bar is working however the label is not changing in my ProgressChanged method i.e. It should say Processing File1 then change to Processing File2, but it stays on the default of Processing.
private void btnProcess_Click(object sender, EventArgs e)
{
toolStripProgressBar1.Visible = true;
toolStripProgressBar1.Maximum = 1000000000;
panel1.Text = "Processing "; // this appears properly
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(processFiles);
worker.ProgressChanged += ProgressChanged;
worker.RunWorkerAsync();
while (worker.IsBusy)
{
// the reason for this is because nothing can happen until the processing is done
toolStripProgressBar1.Increment(1);
}
// more processing
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
panel1.Text = "Processing "+ e.UserState.ToString(); <<<---- This is Not Updating panel1.Text but it evaluates properly
}
private void processFiles(object sender, EventArgs e)
{
int retVal = 0;
foreach (string fileName in listBox1.Items)
{
ProgressChangedEventArgs ea = new ProgressChangedEventArgs(1,fileName);
ProgressChanged(this, ea);
// do more processing
}
}
I would appreciate any help.
You are using the same thread, which is being blocked by another process. You need to use a Task to create a new thread and possibly use Dispatcher.BeginIvoke if the control is on the other thread. Make sure whatever Button Click, etc is happening is marked with the Async keyword as well to make it Asynchronous.
Example:
Await Task mytask = Task.Run(() =>
for(var i = 0; i < 1000; i++)
{
Label.Dispatcher.BeginInvoke( () =>
UpdateMe(int i, LabelClass/Component class/component)});
Then inside the Label Class or wherever the label is:
Public void UpdateMe(int i, LabelClass class)
{
class.label.content = Cint((i/Total)*100);
Thread.Sleep(500);
}
There are other ways to do it as well such as Binding the value to the UI, but this will give you a better understanding of why its not working and how things work with other threads.
If you want to really get a visual understanding call:
`Console.WriteLine($"Current Thread ID: System.Threading.Thread.CurrentThread.ManagedThreadId}");`
Right before you go into the Task---it will give you the main thread ID
Then inside the Task call it again...this will give you the secondary thread ID.
Then Right before the Dispatcher call:
Console.WriteLine($"Do I have access to the label on this thread? {Label.Dispatcher.CheckAccess()}";
If you have access it will display True, if not it will display False...In your case it will display false because its owned by the other thread, but you can use the Dispatcher to be able to do work on that thread while in another thread...
Also, I recommend you not use Background Worker and use Tasks instead...this explains why in depth...basically Tasks do everything Background workers do and more, have less issues and are easier to work with...
http://blog.stephencleary.com/2013/09/taskrun-vs-backgroundworker-conclusion.html
As already commented by Ivan, remove the while loop while (worker.IsBusy) as it's blocking the UI thread to process further. As well, you should enable the WorkerReportsProgress to true
worker.WorkerReportsProgress = true;
worker.ProgressChanged += ProgressChanged;
while (!worker.IsBusy)
{
worker.RunWorkerAsync();
}
Per your comment, move those later processing to BackgroundWorker.RunWorkerCompleted Event
Below is the method to start a thread in compact framework 3.5
public ScanEntry(string scanId)
{
InitializeComponent();
_scanId = scanId;
//reader = deviceFactory.Create();
//reader.YMEvent += new ScanEventHandler(reader_Reading);
//reader.Enable();
}
private void CasesEntry_Load(object sender, EventArgs e)
{
caseCounterLabel.Text = cases.Count.ToString();
scanIdValueLabel.Text = _scanId;
}
internal void menuItemNewScan_Click(object sender, EventArgs e)
{
System.Threading.ThreadStart threadDelegate = new System.Threading.ThreadStart(ScanEvents);
System.Threading.Thread newThread = new System.Threading.Thread(threadDelegate);
newThread.Start();
}
which calls the below method on thread
private void ScanEvents()
{
try
{
//some other codes
if (scanIdValueLabel.InvokeRequired)
{
scanIdValueLabel.Invoke((Action)(() => scanIdValueLabel.Text = "value"));
}
attributeNode = docEventFile.CreateNode(XmlNodeType.Element, "Attribute", string.Empty);
XMLUtils.CreateAttribute(docEventFile, attributeNode, "name", "SCANID");
XMLUtils.CreateAttribute(docEventFile, attributeNode, "value", scanIdValueLabel.Text);
attributeSetNode.AppendChild(attributeNode);
//some other codes
}
catch(Execption e)
{
Message.Show(e.Message);
}
}
Errors:
TextAlign = 'scanIdValueLabel.TextAlign' threw an exception of type 'System.NotSupportedException'
base {System.SystemException} = {"Control.Invoke must be used to interact with controls created on a separate thread."}
In Line
XMLUtils.CreateAttribute(docEventFile, attributeNode, "value", scanIdValueLabel.Text);
I am getting Control.Invoke must be used to interact with controls created on a separate thread at this line
XMLUtils.CreateAttribute(docEventFile, attributeNode, "value", scanIdValueLabel.Text);
I have googled and tried with that solutions but not worked for me.Can any one help me in doing this.
Thanks
When you're dealing with Winforms, WPF, Silverlight there's the following sentence which is very important:
The UI elements can only be accessed by the UI thread. WinForms, WPF, Silverlight doesn't allow access to controls from multiple threads.
However, there is a solution which can be found here:
Update: I've created a sample application to make some things clear:
I've created a form first with a button and a label on it. The label is not visible because it doesn't contain text, but it's right underneath the button.
Scenario 1: Updating without threads:
private void btnStartThread_Click(object sender, EventArgs e)
{
lblMessage.Text = "Button has been clicked.";
}
Off course this is not a problem. It's some standard code:
Scenario 2: Updating with threads:
private void btnStartThread_Click(object sender, EventArgs e)
{
System.Threading.ThreadStart threadDelegate = new System.Threading.ThreadStart(ScanEvents);
System.Threading.Thread newThread = new System.Threading.Thread(threadDelegate);
newThread.Start();
}
private void ScanEvents()
{
lblMessage.Text = "Exected in another thread.";
}
This will fail because I'm MODIFYING the controls on my form from another thread:
Now, I will modify the code so that I'm changing the label with an action through an invoke on the label.
private void ScanEvents()
{
if (lblMessage.InvokeRequired)
{
lblMessage.Invoke((Action)(() => lblMessage.Text = "This text was placed from within a thread."));
}
}
This will make the text change.
So, I hope that it helps. If not, please shout :-)
I am developing an application which will actively read from a file and analyze and display information in that file to the UI in real time.
Everything I have read tells me I should use some kind of threading to do this. I have explored background workers and also tried creating a separate thread at run time and using that thread to update all of the UI elements.
The problem comes in when I can't (or shouldn't) be making cross thread calls, as the original UI elements were created on a different thread.
Is there a way to create these UI elements on the thread that will be updating them? What is the best way to do this?
edit: There was a reply to this post (which is now gone) explaining how I should do this. After updating my code with the method described
This is the updated code I have used. Everything is fine until I add in the file system watcher. As soon as I add that I get the same error about not making cross thread calls.
Session is a class I created that parses through the log file
private Session s1 = new Session("");
private FileSystemWatcher fsw;
private OpenFileDialog ofd1 = new OpenFileDialog();
private BackgroundWorker bgw;
private bool logActive = false;
public frmMain()
{
InitializeComponent();
bgw = new BackgroundWorker();
bgw.WorkerReportsProgress = true;
bgw.ProgressChanged += HandleProgressChanged;
bgw.DoWork += HandleDoWork;
fsw = new FileSystemWatcher(#"H:\Logs", "*.txt");
fsw.SynchronizingObject = this;
fsw.IncludeSubdirectories = false;
fsw.EnableRaisingEvents = true;
fsw.NotifyFilter = NotifyFilters.Size;
fsw.Changed += new FileSystemEventHandler(fsw_OnChanged);
}
private void frmMain_Load(object sender, EventArgs e)
{
ofd1.Filter = "log files (*.txt)|*.txt|All files (*.*)|*.*";
ofd1.FilterIndex = 2;
ofd1.RestoreDirectory = true;
}
private void fsw_OnChanged(object source, System.IO.FileSystemEventArgs e)
{
bgw.RunWorkerAsync();
}
// this runs on the UI thread
// here's where you update the UI based on the information from the event args
private void HandleProgressChanged(object sender, ProgressChangedEventArgs e)
{
for (int i = s1.previousLineNumber; i < s1.GetMessageCount(); i++)
{
ListViewItem lvi = new ListViewItem((s1.GetMessage(i).date).ToString());
lvi.SubItems.Add(s1.GetMessage(i).type.ToString());
lvi.SubItems.Add(s1.GetMessage(i).data);
listView1.Items.Add(lvi);
}
}
// this runs on a background thread; you cannot modify UI controls here
private void HandleDoWork(object sender, DoWorkEventArgs e)
{
s1.ParseLiveFile();
bgw.ReportProgress(100);
}
In order to update the UI you should use Invoke or BeginInvoke.
void LengthyProcessInThread()
{
...
foreach(var item in file)
{
Invoke(delegate() {
.. Update UI here.
});
}
}
Invoke is a method on a control, Eg. the form containing the UI.
I wish you good luck with your quest.
I have a program that has stock quotes pushed to me via an API. The program also has a front end, made in XAML, that freezes while this program is running (i.e. processing the information that the API is sending me). I've tried using Dispatcher.Invoke and/or BackgroundWorker and have read up on threading plenty, but can't get it to unfreeze. Perhaps I'm just doing something wrong. I've attached the relevant code here. Was hoping someone could help.
private void QuoteUpdate(QuoteInfo info)
{
BackgroundWorker bwQuoteUpdate = new BackgroundWorker();
bwQuoteUpdate = new BackgroundWorker();
bwQuoteUpdate.WorkerSupportsCancellation = true;
bwQuoteUpdate.DoWork += bwQuoteUpdate_DoWork;
bwQuoteUpdate.RunWorkerAsync(info);
}
private void bwQuoteUpdate_DoWork(object sender, DoWorkEventArgs e)
{
try
{
Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadStart(() =>
{
QuoteInfo info = e.Argument as QuoteInfo;
//logical functions and work are here
}));
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show("Error in QuoteUpdate: " + ex.Message, "Exception Thrown");
}
}
Although you’re creating a BackgroundWorker with the intention of executing your long-running task on a background thread, you’re still dispatching all your processing back onto the UI thread.
private void bwQuoteUpdate_DoWork(object sender, DoWorkEventArgs e)
{
// Code here runs on background thread.
Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadStart(() =>
{
// Code here runs on UI thread.
}));
}
What you need to do is first perform your calculations on the background thread, but do not update any UI components; rather, store all your results in local variables. Then, once that’s done, use the Dispatcher.Invoke to dispatch control back to the UI thread, and use the results stored in your local variables to update your UI.
For example:
private void bwQuoteUpdate_DoWork(object sender, DoWorkEventArgs e)
{
// Code here runs on background thread.
QuoteInfo info = e.Argument as QuoteInfo;
string result = PerformLongRunningProcessing(info);
Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadStart(() =>
{
// Code here runs on UI thread.
this.resultTextBox.Text = result;
}));
}
Yes, you are doing something wrong. The computation should be done in thread alone add only UI changes should be done in Dispatcher.Invoke.
And if you use DataBinding through INotifyPropertyChange, then drop the Dispatcher.Invoke completly, because marshaling the changes to UI thread is done automaticaly.
Try
Dispatcher.BeginInvoke(...)
I have a MainFrame window with imageViewer control on it. Also there is my dll which calculates changes for the image all was working fine before I decided to add ProgressDialog.(( The Idea was - firstly I am loading the image via dll to main frame (this still OK). Then if user clicks button then show ProgressDialog and in worker.DoWork create new image via the same dllwrapper class (I am using "new")
All seems to be ok but when i am trying to set my currentImage property of imageviewer control (this is nothing more then setter for Image)it show me this error!
This is the code of my userButtonClickHandler from where I am launching ProgressDialog:
void OnThumbnailClick(object sender, RoutedEventArgs e)
{
pd = new ProgressDlg();
pd.Cancel += CancelProcess;
int max = 1000;
System.Windows.Threading.Dispatcher pdDispatcher = pd.Dispatcher;
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
LibWrap lwrap = new LibWrap();//!NEW instance for dll wrapper!
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
imageViewer.CurrentImage = lwrap.engine2(BitmapFrame.Create(MyPrj.App.draggedImage));//ERROR IS HERE!!!//The calling thread cannot access this object because a different thread owns it.
//what process??
};
worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
pd.Close();
};
worker.RunWorkerAsync();
pd.ShowDialog();
}
There is function from the same MainFrame class for canceling (There is OK too)
void CancelProcess(object sender, EventArgs e)
{
worker.CancelAsync();
}
This is class for ProgressDlg (it has nothing more then progress bar and cancel button):
public partial class ProgressDlg : Window
{
public ProgressDlg()
{
InitializeComponent();
}
public string ProgressText
{
set
{
this.lblProgress.Content = value;
}
}
public int ProgressValue
{
set
{
this.progress.Value = value;
}
}
public event EventHandler Cancel = delegate { };
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
Cancel(sender, e);
}
}
}
I am working with this problem for (almost) two days and still couldn't find the solution. Help me please if you have an idea.
1 UPDATE
It seems to me that you was right about this threads - when I am trying to load previously loaded(initial) image (from the main thread) -it loads OK but if I am trying libWrap it fails due to processes conflict!
worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
imageViewer.Width = 1000;//work!
imageViewer.CurrentImage = MyPrj.App.draggedImage;//Work!
imageViewer.CurrentImage = lwrap.engine2(BitmapFrame.Create(MyPrj.App.draggedImage));//Fail =(!
}
2 UPDATE
I have tried this construction OnThumbnailClick
Application.Current.MainWindow.Dispatcher.BeginInvoke(new Action(() =>
{
imaeViewer.CurrentImage = lwrap.engine2(BitmapFrame.Create(FXPhotoStudio.App.draggedImage));
}
This caused same error/ Perhaps it will be correct to pass this value in MainThread (UI)? But I have no idea how.( I couldnot use serializers - becouse it is rapidly calling operation and this images are temporary/
WPF cannot alter items that were created on another thread.
So if you create an ImageViewer on one thread, you cannot alter it's properties on another thread.
Instead, use the Dispatcher, which is WPF's internal message queue for the main UI thread, to update your objects.
Or, use Henk's Answer to do your work on another thread, but return the result to the main thread so it can update your ImageViewer's properties
You need at least these changes:
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
args.Result = lwrap.engine2(BitmapFrame.Create(MyPrj.App.draggedImage));
};
worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
if (args.Error != null)
{ ... } // handle error
else if (args.Cancelled)
{ ... } // handle Cancel
else
{
imageViewer.CurrentImage = args.Result;
}
pd.Close();
}
I'm not sure if it's enough but try again.
The imageViewer was created on the main thread of the application (which is appropriate because it is a UI control). UI controls can ONLY be accessed by the thread which created it, and that thread must have its own dispatcher (by which I mean message loop).
Remove the threading code, and it will work.
If you want this to popup the window and then show the image when the conversion completes, you will have to store the returned image in a variable until you return to the main thread, and then make the assignment to the imageViewer.