问题
Does a System.Windows.Threading.Dispatcher work on the UI-thread of a WinForms
application?
If yes, why? It is coming from WindowsBase.dll which seems to be a WPF
component.
If not, how can I invoke work units back onto the UI-thread? I\'ve found Control.BeginInvoke(), but it seems clumsy to create a control only to reference the originating thread.
回答1:
You can use Dispatcher
even in a WinForms app.
If you are sure to be on a UI thread (e.g. in an button.Click handler), Dispatcher.CurrentDispatcher
gives you the UI thread dispatcher that you can later use to dispatch from background threads to the UI thread as usual.
回答2:
Dispatcher is a WPF component, not a WinForms component.
If you want to dispatch work items on the UI thread, then you would have to either use Control.BeginInvoke as you've already found, or react to ResetEvents/WaitObjects across threads.
Usually invoking work items on the UI thread is a bad thing unless it's a UI piece of work (ie. updating a control's content or something) in which case the Control.BeginInvoke() would be sufficient.
回答3:
I provided an example of using System.Windows.Threading.Dispatcher
in Windows Form in my answer to question "Parallel Programming using TPL on WinForms" since the previous answer to your question:
If you are sure to be in UI thread (eg. in an button.Click handler), Dispatcher.CurrentDispatcher gives you the UI thread dispatcher that you can use to dispatch from background thread to UI thread as usual.
is either misleading or confusing or lacks the concrete usage context:
button.Click
handler does not assure to be on UI thread;- if you are not on UI thread, it is still possible to use the disparcher of UI thread of WinForms form
One can get dispatcher of WinForm UI thread:
Dispatcher dispatcherUI = Dispatcher.CurrentDispatcher;
in either button click event handler or anywhere else (in form constructor)
And then use it to execute on UI from other threads, see more details on example below in my answer:
private void button1_Click(object sender, EventArgs e)
{
Dispatcher dispUI = Dispatcher.CurrentDispatcher;
for (int i = 2; i < 20; i++)
{
int j = i;
var t = Task.Factory.StartNew
(() =>
{
var result = SumRootN(j);
dispUI.BeginInvoke
(new Action
(() => richTextBox1.Text += "root " + j.ToString()
+ " " + result.ToString() + Environment.NewLine
)
, null
);
}
);
}
回答4:
Use background worker thread as it's UI message pump aware, This MSDN Article though mostly about WPF does state that the BWT is UI aware even for windows forms.
回答5:
I had similar problem using Oracle dependency class which runs on its own thread within Winforms,
When OnChange event fired from Oracle Dependency, I wanted to show the changes in DataGridView by simply setting DataSource to eventargs.Details (which is essentially a DataTable), and it throws: System.InvalidOperationException was unhandled by user code Message=Cross-thread operation not valid: Control 'dataGridView1' accessed from a thread other than the thread it was created on.
StackOverflow user Brian Peiris (bpeiris@gmail.com) ,collegue of mine showed me this way around:
void dep_OnChange(object sender, OracleNotificationEventArgs arg)
{
Console.WriteLine("Notification received");
int infoSum = int.Parse(arg.Details.Compute("Sum(Info)", "Info is not null").ToString());
InfoSum x = (InfoSum)infoSum;
foreach (DataRow dr in arg.Details.Rows)
{
Console.WriteLine(string.Format("Operation(InfoSum)= {0}", Enum.GetName(typeof(InfoSum), x)));
Console.WriteLine(string.Format("ontable={0} Rowid={1},info={2}", dr.Field<string>("ResourceName"), dr.Field<string>("rowid"), dr.Field<Int32>("info")));
}
// Following will throw cross-thread
// dataGridView1.DataSource = arg.Details;
// instead of line above use the following
dataGridView1.BeginInvoke((Action)(()=>dataGridView1.DataSource = arg.Details));
IsNotified = true;
}
}
回答6:
Take a look at backgrounder and see if it fits your needs.
回答7:
Sometimes a Timer component is useful and easy to setup in WinForms, just set its interval and then enable it, then make sure the first thing you do in its Tick event handler is to disable itself.
I think Timer runs the code in its own thread, so you may still need to do a BeginInvoke (called upon the WinForm object [this]) to run your Action.
private WebBrowserDocumentCompletedEventHandler handler; //need to make it a class field for the handler below (anonymous delegates seem to capture state at point of definition, so they can't capture their own reference)
private string imageFilename;
private bool exit;
public void CaptureScreenshot(Uri address = null, string imageFilename = null, int msecDelay = 0, bool exit = false)
{
handler = (s, e) =>
{
webBrowser.DocumentCompleted -= handler; //must do first
this.imageFilename = imageFilename;
this.exit = exit;
timerScreenshot.Interval = (msecDelay > 0)? msecDelay : 1;
timerScreenshot.Enabled = true;
};
webBrowser.DocumentCompleted += handler;
Go(address); //if address == null, will use URL from UI
}
private void timerScreenshot_Tick(object sender, EventArgs e)
{
timerScreenshot.Enabled = false; //must do first
BeginInvoke((Action)(() => //Invoke at UI thread
{ //run in UI thread
BringToFront();
Bitmap bitmap = webBrowser.GetScreenshot();
if (imageFilename == null)
imageFilename = bitmap.ShowSaveFileDialog();
if (imageFilename != null)
{
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(imageFilename))); //create any parent directories needed
bitmap.Save(imageFilename);
}
bitmap.Dispose(); //release bitmap resources
if (exit)
Close(); //this should close the app, since this is the main form
}), null);
}
you can see the above in action at WebCapture tool (http://gallery.clipflair.net/WebCapture, source code at: http://ClipFlair.codeplex.com, see Tools/WebCapture folder) that grabs screenshots from websites. BTW, if you want to call the executable from command-line make sure you go to Properties of the project and at Security tab turn-off ClickOnce security (else it can't access command-line)
来源:https://stackoverflow.com/questions/303116/system-windows-threading-dispatcher-and-winforms