(C #) secure, simple Windows Forms multithreaded programming 1

Source: Internet
Author: User
Tags assert final functions new set reference thread tostring
window| Security | programming | multithreading

To be honest, what I originally intended to do was irrelevant to what was discussed in the main article. At that time, the first time I found out I needed to be in. NET to calculate the area of a circle, of course, first need a PI (pi) of the exact value. System.Math.PI is very convenient to use, but it only provides 20-bit precision, I can't help but worry about the accuracy of calculation (in fact, 21-bit can definitely make me feel comfortable). So, like any other competent programmer, I forgot the real problem, and I buried myself in a program that I liked to work out the PI value of any number of decimal digits. The final result is shown in Figure 1.


Figure 1. Program to calculate PI value

Progress of time-consuming operations (long-running Operations)

Although most programs do not need to calculate PI values, many programs require time-consuming operations such as printing, invoking a Web service, or calculating interest income for a Pacific Northwest billionaire. For the user, as long as you can see the current completion of the progress, such a waiting is usually acceptable, or even a get out of other things. So I added a progress bar (progress bar) to each of my small programs. The algorithm that I used to compute the PI value is calculated 9 digits at a time. Once a new set of numbers is calculated, my program updates the TextBox control and moves ProgressBar to show our progress. For example, Figure 2 is the case where the 1000-bit pi value is being computed (if 21 is not a problem, 1000 bits will certainly be better).


Figure 2. Calculating 1000-bit pi value

The following code shows how the user interface (UI) is updated after the number of pi values is computed:

void ShowProgress (string pi, int totaldigits, int digitssofar) {

_pi. Text = PI;

_piprogress.maximum = totaldigits;

_piprogress.value = Digitssofar;

}

void calcpi (int digits) {

StringBuilder pi = new StringBuilder ("3", digits + 2);

Show progress

ShowProgress (pi. ToString (), digits, 0);

if (digits > 0) {

Pi. Append (".");

for (int i = 0; i < digits i + + 9) {

int ninedigits = Ninedigitsofpi.startingat (i+1);

int digitcount = Math.min (digits-i, 9);

String ds = String. Format ("{0:d9}", ninedigits);

Pi. Append (ds. Substring (0, Digitcount));

Show progress

ShowProgress (pi. ToString (), digits, i + digitcount);

}

}

}

Everything went smoothly until I switched to the other program in the 1000-bit calculation of PI and then switched it back ..... I saw the picture shown in Figure 3:

Figure 3. There's no paint incident.

The root of the problem is, of course, that our program is single-threaded. When this thread is busy calculating the PI value, there is no chance to draw the UI. The reason I didn't have this problem is because when I set the TextBox.Text properties and the Progressbar.value properties, these controls, as part of the Set property action, can force their drawing operations to take place immediately (although I noticed the progress Bar is better than text box. However, when I put this program backstage and then back to the foreground, I need to redraw the entire client area, which is a paint event for the window. Because the event that is currently being processed (the Click event of the Calc button) is not processed until the other event is returned, we have no chance to see any more progress shown. What I need to do now is to release the UI thread to do the UI work and put the time-consuming operation back into the background. In order to achieve this goal, we need another thread.

Asynchronous operations

Now my sync Click handler looks like this:

void _calcButton_Click (object sender, EventArgs e) {

CALCPI (int) _digits. Value);

}

Recall that our problem is "until CALCPI returns, the thread can return from click Handler, and the window has the opportunity to handle paint (or other) events." One way to handle this situation is to start another thread, such as:

Using System.Threading;

int _digitstocalc = 0;

void Calcpithreadstart () {

CALCPI (_DIGITSTOCALC);

}

void _calcButton_Click (object sender, EventArgs e) {

_digitstocalc = (int) _digits. Value;

Thread pithread = new Thread (new ThreadStart (Calcpithreadstart));

Pithread.start ();

}

Now, without waiting for CALCPI to finish before the button click event returns, I created and started a new thread, the Thread.Start method will dispatch and start a new thread, and then return immediately to let the UI thread back to its own work. Now if the user interacts with the program (for example, in the background, in the foreground, changing the window size, closing it), the UI thread is free to handle these events, and the worker thread is also working on its calculated pi. Figure 4 shows the case of two threads working:

Figure 4. Naïve multithreading

You may have noticed that I did not pass any arguments for the entry point--calcpithreadstart of the worker thread, but instead put the number of digits to be computed into a field (field) _digitstocalc, calling the entry point of the thread, CALCPI called. It's a pain, and one of the reasons I like to use delegate for asynchronous work. The delegate support parameter avoids the intense ideological struggle for adding an extra temp field and an extra function.

If you are unfamiliar with delegate (delegates), consider them to be objects that call static or instance functions. Their declaration syntax and functions are the same in C #. For example, a delegate from CALCPI looks like this:

delegate void calcpidelegate (int digits);

Once I have a delegate, I can instantiate an object to call the CALCPI function synchronously:

void _calcButton_Click (object sender, EventArgs e) {

CalcPiDelegate calcpi = new CalcPiDelegate (CALCPI);

CALCPI (int) _digits. Value);

}

Of course, I don't want to call CALCPI synchronously; I want to call it asynchronously. However, before I do this we need to know a little about how the delegate works. The above delegate declaration actually declares a class that inherits from a MulticastDelegate class with three functions (Invoke, BeginInvoke, and EndInvoke), like this:

Class Calcpidelegate:multicastdelegate {

public void Invoke (int digits);

public void BeginInvoke (int digits, AsyncCallback callback,

Object asyncstate);

public void EndInvoke (IAsyncResult result);

}

When I instantiate a CalcPiDelegate object and call it like a function, I actually invoke his synchronous invoke function. It then called my calcpi. However, BeginInvoke and EndInvoke are a pair of functions that allow you to invoke and harvest (harvest) The return value asynchronously. So, in order for CALCPI to run in another thread, I need to invoke BeginInvoke as follows:

void _calcButton_Click (object sender, EventArgs e) {

CalcPiDelegate calcpi = new CalcPiDelegate (CALCPI);

Calcpi.begininvoke (int) _digits. Value, NULL, NULL);

}

Note that we passed NULL for the last two parameters of BeginInvoke. These two parameters are useful when we need the return value of the Harvest function (EndInvoke is used to do this). Because the CALCPI function updates the UI directly, we only need to pass NULL for these two parameters. If you are interested in the details of the delegate (synchronous and asynchronous), you can look at it. NET delegate: A C # Bedtime Story (Chinese translation edition) this article.

By this time, I should be happy. I've shown the time-consuming operation progress in my program and kept the UI good interactivity.

Multithreading security

It looks like I'm lucky enough (or unlucky to see how you look at these things). Microsoft windows (R) XP provides me with a very robust low-level implementation of the Windows system on which Windows Forms is built. It is so robust that it gracefully helps me with all the questions, even if I violate the main Windows programming guidelines-don't manipulate this window on threads other than the one that creates a window. Unfortunately, there is no guarantee that other, less robust Windows implementations will not give me the same elegance of face.

The problem is, of course, my own cause. Recall Figure 4, I use two threads to access a window at the same time. However, because time-consuming operations are so prevalent in Windows programs, Windows Each UI class in Forms (each class that is essentially inherited from System.Windows.Forms.Control) has a property invokerequired that can be accessed securely in any thread. This property returns True when a thread calls the control's object method before it is marshaled to the thread that created the control. The simple addition of an assert in my showprogress function immediately reveals the error of my approach.

Using System.Diagnostics;

void ShowProgress (string pi, int totaldigits, int digitssofar) {

Make sure we ' re on the right thread

Debug.Assert (_PI. InvokeRequired = = false);

...

}

Practical,. NET document is very clear on this point. It describes the following: "There are four methods on the control that can be safely invoked from any thread: Invoke, BeginInvoke, EndInvoke, and CreateGraphics. For all other method invocations, you should use one of these invoke methods when calling from another thread. "So when I set the control properties, it definitely violates this rule. The first three functions explicitly indicate that I need to build another delegate that executes in the UI thread. If I don't want to block my worker threads like blocking the UI thread, I need to use asynchronous BeginInvoke and EndInvoke. However, since my worker thread was born to serve the UI thread, let the world be simpler by using the synchronous invoke method. Like the following:

public Object Invoke (Delegate method);

public Object Invoke (Delegate method, object[] args);

The first overloaded invoke receives an instance of the delegate that contains the method that we will call in the UI thread, and the delegate (or method) must have no arguments. However, the function we want to use to update the UI showprogress with three parameters, so we need a second overload form. We also need to define a separate delegate for our ShowProgress method so that we can pass parameters correctly. Here's how to use invoke to make sure that the showprogress we're calling also includes the way we use the window to execute in the correct thread: (make sure to replace two calls to ShowProgress in the CALCPI).

Delegate

void ShowProgressDelegate (string pi, int totaldigits, int digitssofar);

void calcpi (int digits) {

StringBuilder pi = new StringBuilder ("3", digits + 2);

Preparing to display progress asynchronously

ShowProgressDelegate showprogress =

New ShowProgressDelegate (showprogress);

Show progress

This. Invoke (showprogress, new object[] {pi. ToString (), digits, 0});

if (digits > 0) {

Pi. Append (".");

for (int i = 0; i < digits i + + 9) {

...

Show progress

This. Invoke (ShowProgress,

New object[] {pi. ToString (), digits, i + digitcount});

}

}

}

The use of invoke ultimately gives me the security to use multithreading in Windows Forms programs. The UI thread hatches a worker thread to perform time-consuming operations, and the worker thread passes control back to the UI thread when the UI needs to be updated. Figure 5 shows our secure multithreaded architecture.

Figure 5. Multi-thread security

Simplified multithreading

The call to invoke was a bit cumbersome because it was called two times in calcpi, and I could simplify and improve the showprogress to make asynchronous calls on its own. If ShowProgress is invoked on the correct thread, he will update the control, but if it is invoked in an incorrect thread, it will use invoke to call itself back in the correct thread. This makes us go back to the simple calcpi of the past:

void ShowProgress (string pi, int totaldigits, int digitssofar) {

Make sure we're in the right thread

if (_PI. InvokeRequired = = False) {

_pi. Text = PI;

_piprogress.maximum = totaldigits;

_piprogress.value = Digitssofar;

}

else {

Show Progress asynchronously

ShowProgressDelegate showprogress =

New ShowProgressDelegate (showprogress);

This. Invoke (ShowProgress,

New object[] {pi, totaldigits, Digitssofar});

}

}

void calcpi (int digits) {

StringBuilder pi = new StringBuilder ("3", digits + 2);

Show progress

ShowProgress (pi. ToString (), digits, 0);

if (digits > 0) {

Pi. Append (".");

for (int i = 0; i < digits i + + 9) {

...

Show progress

ShowProgress (pi. ToString (), digits, i + digitcount);

}

}

}

Because invoke is an asynchronous invocation and we do not really need its return value (in fact, showprogress itself does not return any value), it is best to use BeginInvoke so that the worker thread does not block. Just like the following:

BeginInvoke (showprogress, new object[] {pi, totaldigits, Digitssofar});

If the return value of a function call is not required, BeginInvoke should always give priority because it allows the worker thread to immediately return to its work and avoid the possibility of deadlock.

What have we done?

I used a short example of how to show progress while performing time-consuming operations and to keep the UI user action responsive. To complete this task, I used an asynchronous delegate to "Hatch" a worker thread, an invoke method on the main window, and another proxy to be marshaled back to the UI thread execution.

One thing I'm very careful to avoid is to share data between the UI thread and the worker thread. Instead, I pass copies of the required data (the number of digits to be counted, the numbers and progress that have been calculated). In the final solution, I have never passed a reference to an object, such as a reference to the current StringBuilder (although passing a reference saves a copy of the string every time it returns to the UI thread). If I were to pass a reference to data between two threads, I would have to use. NET original synchronization means to ensure that only one thread accesses an object at any one time, which can add a lot of work. This is enough, without introducing a synchronous system.

Of course, if you need to deal with a database, you certainly don't intend to replicate the database everywhere. However, in Windows Forms, if possible, I recommend that you use asynchronous delegates and messages between worker threads and UI threads to implement time-consuming operations.

Download Example: AsynchCalcPi.exe



Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.