Reputation: 5007
Explanation & Background: Sorry if this question sucks, but I am trying to wrap my head around Tasks. I currently have a set of classes that will take a control into their constructors, and allow for me to automate user interaction (typing into textbox, clicking button, etc.).
An example of one of these is my class called TextboxTester
. Here is a snippet of how I bring in the textbox:
public class TextboxTester
{
private int _currentTextLength = 0;
private string _text;
private TextBox _textBox;
private Timer timer;
#region Constructor
public TextboxTester(TextBox TextBox)
{
_textBox = TextBox;
}
#endregion
I want to be able to chain actions(tasks) together in order - one after another so I can automate a series of user input events. I read up and found out about TPL tasks, and decided to give it a spin.
I created my TextboxTester
class and my ButtonTester
class, and passed in the controls. I want to be able to type into the textbox, and then click the button - so I call this:
Task.Factory.StartNew(() => txtTester.WriteText("Hello World")).Wait();
Task.Factory.StartNew(() => btnTester.Click(1));
From my knowledge of the Task.Factory calls, this is what I want to do - perform the first action, wait until it is done - and then perform the next. The problem is that TextboxTester.WriteText()
uses a timer to emulate typing into a TextBox
(1 character per second):
public void WriteText(string Text)
{
if (timer == null)
{
State.Working = true;
timer = new Timer();
try
{
_text = Text;
timer.Elapsed += new ElapsedEventHandler(timer_ElapsedWrite);
timer.Interval = 1000;
timer.Enabled = true;
timer.Start();
}
catch
{
MessageBox.Show("WriteText timer could not be started.");
}
}
}
void timer_ElapsedWrite(object sender, ElapsedEventArgs e)
{
_textBox.Dispatcher.BeginInvoke(new Action(() =>
{
TextBoxAutomationPeer peer = new TextBoxAutomationPeer(_textBox);
IValueProvider valueProvider = peer.GetPattern(PatternInterface.Value) as IValueProvider;
valueProvider.SetValue(_text.Substring(0, _currentTextLength));
if (_currentTextLength == _text.Length)
{
timer.Stop();
State.Working = false;
timer = null;
return;
}
_currentTextLength++;
}));
}
**The _textBox.Dispatcher
call in the elapsed event was to prevent a "thread already owns this object" message that was occurring.
Finally the problem: The Task.Factory.StartNew() call does not seem to take into account the time_elapsed event that is still occurring. I want to be able to finish out the events before the Task considers "WriteText" is complete.
and Question: Is there a way to tell a task to be able to take these types of things into account? Am I not understanding Tasks?
EDIT - I am using 3.5 and a 3.5 implementation of TPL
Upvotes: 3
Views: 1470
Reputation: 34427
Right now you are getting no benefits from using tasks. Making WriteText
an async method can make things much easier:
public async Task WriteText(string text)
{
TextBoxAutomationPeer peer = new TextBoxAutomationPeer(_textBox);
IValueProvider valueProvider = peer.GetPattern(PatternInterface.Value) as IValueProvider;
for(int i = 1; i < text.Length; ++i)
{
await Task.Delay(1000); // no need for timer
valueProvider.SetValue(text.Substring(0, i));
}
}
So you can write
async void TestMethod()
{
await txtTester.WriteText("Hello World"));
Task.Factory.StartNew(() => btnTester.Click(1)); // probably sould be refactored too
}
Edit
Version without async/await with minimal changes to existing code:
public class TextboxTester
{
/* unchanged code */
// unfortunately, there is no non-generic version of this class
// basically it allows us to 'manually' complete Task
private TaskCompletionSource<object> _tcs;
public Task WriteText(string Text)
{
if (timer == null)
{
_tcs = new TaskCompletionSource<object>();
/* unchanged code */
}
return _tcs.Task; // return task which tracks work completion
}
/* unchanged code */
if (_currentTextLength == _text.Length)
{
timer.Stop();
State.Working = false;
timer = null;
_tcs.TrySetResult(null); // notify completion
return;
}
/* unchanged code */
}
Now you can write
// well, you could simply make this a blocking method instead
txtTester.WriteText("Hello World").Wait();
btnTester.Click(1);
or better
txtTester.WriteText("Hello World")
.ContinueWith(t => btnTester.Click(1)); // does not block calling thread
Main idea is to design your async method in a way which allows reporting completion. There are 3 common patterns for that:
BeginWriteText
, which returns IAsyncResult
and EndWriteText
, which accepts IAsyncResult
)public event EventHandler WriteTextCompleted
)Task
)These approaches are well described in Task-based Asynchronous Pattern
Upvotes: 2