Posted by: Xiaonan Ji | April 7, 2010

BeginInvoke is asynchronized!

BeginInvoke is a method you will call when you want to update a UI control within a thread other than the one the control is created. It is enforced by .net. For example, you have a TextBox control tb and you want to put a new text in it, normally, you will make a function:

void DisplayNewText(string text) { tb.Text = text; }

You may want to update your TextBox in a thread rather than the main thread. Usually, your TextBox control is created in the main thread, if so, you cannot call the above method in your sub-thread. In order to do so, you need to rewrite your above method to:

void DisplayNewText(string text) {
if(tb.InvokeRequired) BeginInvoke(new TextBoxDelegate(DisplayNewText), new object[] { text });
else tb.Text = text;
}

You need a delegate here:

delegate void TextBoxDelegate(string text);

The new DisplayNewText method first tests whether the call is made from the thread that the TextBox belongs to, if so, directly execute the else part. If not, it wraps itself into a delegate and pass the call of the delegate to the main thread, where the else part is executed then.

There is a point that I would like to make: the BeginInvoke method is execuated asynchronized. This actually means that it is not guranteed that the else part is executed immediately after the BeginInvoke call. This, actually may cause problem.

In a real product code, we put logs into a TextBox. New logs are put to TextBox in a sub-thread. To put the logs in better format, we use indentions to separate log sections. An example log section is as below:

[Optimization Begins]

Initial value: 0.345

Satisfied constraints: 3 of 3

Objective: 0.5

[Optimization Ends]

Our log system is consisted by many sections and sections can contain sections. To implement the indention, we use a stack to hold sections. Whenever a new section starts, we push it to the stack and whenever a section finishes, we pop it from the stack. When a log is added to the log system, we investigate the total numbers of sections held in the stack, to decide how many “\t” to put in front of each line of log:


for(int i = 1; i < Logging.NumSections; i++) tb.AppendText("\t");
tb.AppendText(logMessage);

Then we found that some lines have wrong indention:

[Optimization Begines]

Initial value: 0.345

Satisfied constraints: 3 of 3

Objective: 0.5

[Optimization Ends]

In this example, clearly, Objective line’s indention is wrong. After investigation, we found that it is the BeginInvoke that causes this problem. Since BeginInvoke is asynchronized, it happens that after the BeingInvoke is called to display the Objective line, the thread hangs there and the main thread executes the exit of the section. Then when the else part is called from the BeginInvoke, the Logging.NumSections is short by one.

Now the story is, we should pass in whatever values we need at the time when BeginInvoke is called, rather than depends on some outside holder (here is the Logging class) to retrieve the value (here is the NumSections). If we use Logging.NumSections to get the number of sections held in stack, it is the number of sections held in stack when the else part is executed, rather than when the BeginInvoke is called. Timing is essential what’s going wrong in our story.

Of course, there are cases when the else part is executed, you want to use the value held NOW, rather than the value held when the BeginInvoke is called, in this case, retrieve the value from the outside holder makes sense.

Until next time, happy coding!

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Categories

Follow

Get every new post delivered to your Inbox.