I have a TextBox
with a TextChanged
event that calls a custom event if the text of the textbox represents an existing file. In this event, there is a call to an outside dll that does some processing on the File, which can take upwards of a minute to finish. There is also some post-processing I do, dependent on what result this method returns to me. Currently, this is blocking my UI, which is highly undesirable.
There are essentially 2 "options"/scenarios I see.
- Within the custom event, somehow wait for the dll call to finish, before continuing the event, while also keeping the UI free. This seems like the simplest idea from my multithreading-untrained self, but it also conceptually throws red flags at me: Is this even possible given that the custom event itself (called from
TextChanged
) is on the UI thread? - Throw the entire custom event into it's own thread using
Task.Run()
. Downside here is that apart from the dll method call, there is quite a good amount of UI elements that are affected by getters/setters after the long method. I could write alternated getters/setters based on the appropriateInvokeRequired
, but if there is a more correct way to do this, I'd rather take that approach.
I made a much shorter (although contrived) example project, which shows essentially what I'm after, using option 2 from above:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
comboBox1.Items.Add("Select One...");
comboBox1.Items.Add("Item 1");
comboBox1.Items.Add("Item 2");
Value = 0;
}
public string SetMessage
{
set
{
if (lblInfo.InvokeRequired)
lblInfo.BeginInvoke((MethodInvoker)delegate () { lblInfo.Text = Important ? value + "!" : value; });
else
lblInfo.Text = Important ? value + "!" : value;
}
}
public bool Important
{
get
{
return chkImportant.Checked;
}
set
{
if (chkImportant.InvokeRequired)
chkImportant.BeginInvoke((MethodInvoker) delegate() { chkImportant.Checked = value; });
else
chkImportant.Checked = value;
}
}
public SomeValue Value
{
get
{
if (comboBox1.InvokeRequired)
{
SomeValue v = (SomeValue)comboBox1.Invoke(new Func<SomeValue>(() => SomeValue.Bar));
return v;
}
else
{
switch (comboBox1.SelectedIndex)
{
case 1:
return SomeValue.Foo;
case 2:
return SomeValue.Bar;
default:
return SomeValue.Nothing;
}
}
}
set
{
if (comboBox1.InvokeRequired)
{
comboBox1.BeginInvoke((MethodInvoker)delegate ()
{
switch (value)
{
case SomeValue.Nothing:
comboBox1.SelectedIndex = 0;
break;
case SomeValue.Foo:
comboBox1.SelectedIndex = 1;
break;
case SomeValue.Bar:
comboBox1.SelectedIndex = 2;
break;
}
});
}
else
{
switch (value)
{
case SomeValue.Nothing:
comboBox1.SelectedIndex = 0;
break;
case SomeValue.Foo:
comboBox1.SelectedIndex = 1;
break;
case SomeValue.Bar:
comboBox1.SelectedIndex = 2;
break;
}
}
}
}
private void CustomEvent(object sender, EventArgs e)
{
if (!Important)
Important = true;
SetMessage = "Doing some stuff";
if (Value == SomeValue.Foo)
Debug.WriteLine("Foo selected");
//I don't want to continue until a result is returned,
//but I don't want to block UI either.
if (ReturnsTrueEventually())
{
Debug.WriteLine("True!");
}
Important = false;
SetMessage = "Finished.";
}
public bool ReturnsTrueEventually()
{
//Simulates some long running method call in a dll.
//In reality, I would interpret an integer and return
//an appropriate T/F value based on it.
Thread.Sleep(5000);
return true;
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
//Do I *need* to multithread the whole thing?
Task.Run(() => CustomEvent(this, new EventArgs()));
}
}
public enum SomeValue
{
Nothing = 0,
Foo = 100,
Bar = 200
}
Note: I'm not asking for code review on my option 2 code. Rather, I'm asking if option 2 is necessary to accomplish, since that option causes me to change a considerably larger portion of code, given that it's only 1 method within it holding up the entire process.
I also realize I can simplify some of the code in these properties to prevent replication. For the sake of demonstrating to myself and debugging, I am holding off on that at this time.
Here is what I had related to option 1 (left out duplicate code and the getters/setters without their invokes):
private async void CustomEvent(object sender, EventArgs e)
{
if (!Important)
Important = true;
SetMessage = "Doing some stuff";
if (Value == SomeValue.Foo)
Debug.WriteLine("Foo selected");
//I don't want to continue until a result is returned,
//but I don't want to block UI either.
if (await ReturnsTrueEventually())
{
Debug.WriteLine("True!");
}
Important = false;
SetMessage = "Finished.";
}
public async Task<bool> ReturnsTrueEventually()
{
//Simulates some long running method call in a dll.
//In reality, I would interpret an integer and
//return an appropriate T/F value based on it.
Thread.Sleep(5000);
return true;
}