Reputation: 22973
I have a windows form with a text box txtOutput
. I have some content in it. I have written a property to get and set the txtOutput.Text
both from within the same thread and across threads like this:
public string OutputString
{
get
{
string text = string.Empty;
if (txtOutput.InvokeRequired)
{
txtOutput.BeginInvoke(new MethodInvoker(delegate
{
text = txtOutput.Text;
}));
}
else
{
text = txtOutput.Text;
}
return text;
}
set
{
if (txtOutput.InvokeRequired)
{
txtOutput.BeginInvoke(new MethodInvoker(delegate
{
txtOutput.Text = value;
}));
}
else
{
txtOutput.Text = value;
}
}
}
If I set/get the property from the same thread, the behavior is as expected when calling the below function like PrintMessage()
.
public void PrintMessage()
{
MessageBox.Show(OutputString);
}
But when I call like this new Thread(PrintMessage).Start()
. The get
does not retrieve the value in the text box (i.e., the MessageBox
shows empty string). When I do the same by keeping a breakpoint on the line:
txtOutput.BeginInvoke(new MethodInvoker(delegate
{
text = txtOutput.Text;
}));
while debug, the value is retrieved (i.e., the MessageBox
shows the txtOutput
content)
Should I sleep
somewhere? Where am I making the mistake?
Upvotes: 2
Views: 935
Reputation: 106
You can use Tasks with TaskScheduler form UI thread. When you pass the dispatcher form UI thread to task factory, this task performed in UI thread and return result to the thread where this task was created on.
namespace WpfTest {
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
public partial class MainWindow : Window {
private readonly TaskScheduler _taskScheduler;
public MainWindow() {
InitializeComponent();
_taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
button.Click += ButtonOnClick;
}
public string Text {
get {
if (text.CheckAccess()) {
return text.Text;
}
return Task.Factory.StartNew(
() => text.Text, CancellationToken.None, TaskCreationOptions.None, _taskScheduler).Result;
}
set {
if (text.CheckAccess()) {
text.Text = value;
} else {
Task.Factory.StartNew(
() => { text.Text = value; }, CancellationToken.None, TaskCreationOptions.None, _taskScheduler);
}
}
}
private void ButtonOnClick(object sender, RoutedEventArgs routedEventArgs) {
Text += "Test1";
new Thread(() => { Text += "Test2"; }).Start();
}
}
}
Upvotes: 0
Reputation: 3433
The problem is you're calling MessageBox.Show() with a reference to the text variable before the UI thread can handle the request you placed with the dispatcher. I would avoid using Thread.Sleep() as you could end up with some nasty side effects. Ideally you should re-factor your code to get rid of the property, which is synchronous, in favor of a more asynchronous solution. Something similar to the code below should give you the result you're looking for:
public void PrintMessage(Action<string> displayAction)
{
string text = string.Empty;
if (txtOutput.InvokeRequired)
{
txtOutput.BeginInvoke(new MethodInvoker(delegate
{
displayAction.Invoke(txtOutput.Text);
}));
}
else
{
displayAction.Invoke(txtOutput.Text);
}
}
And Invoke it:
// Get the text asynchronously
PrintMessage(s => MessageBox.Show(s));
Upvotes: 2