<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-weidagang2046的专栏-文章分类-Windows</title><link>http://www.blogjava.net/weidagang2046/category/3039.html</link><description>物格而后知致</description><language>zh-cn</language><lastBuildDate>Fri, 02 Mar 2007 07:33:19 GMT</lastBuildDate><pubDate>Fri, 02 Mar 2007 07:33:19 GMT</pubDate><ttl>60</ttl><item><title>WebBrowser设置Cookie</title><link>http://www.blogjava.net/weidagang2046/articles/95745.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Wed, 24 Jan 2007 07:40:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/95745.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/95745.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/95745.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/95745.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/95745.html</trackback:ping><description><![CDATA[
		<span id="_ctl0_MainContent_PostFlatView">
				<span>
						<font face="Courier New" size="2">public partial class WebBrowserControl : Form<br />    {<br />        private String url;<br /><br />        [DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]<br />        public static extern bool InternetSetCookie(string lpszUrlName, string lbszCookieName, string lpszCookieData);<br /><br />        public WebBrowserControl(String path)<br />        {<br />            this.url = path;<br />            InitializeComponent();<br /><br />            // set cookie<br />            InternetSetCookie(url, "JSESSIONID", Globals.ThisDocument.sessionID);  <br /><br />            // navigate<br />            webBrowser.Navigate(url);  <br />        }<br />        ...<br />}<br /><br />from: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=523069&amp;SiteID=1</font>
						<br />
				</span>
		</span>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/95745.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2007-01-24 15:40 <a href="http://www.blogjava.net/weidagang2046/articles/95745.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Safe, Simple Multithreading in Windows Forms</title><link>http://www.blogjava.net/weidagang2046/articles/92207.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 07 Jan 2007 05:49:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/92207.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/92207.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/92207.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/92207.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/92207.html</trackback:ping><description><![CDATA[
		<p>Chris Sells</p>
		<p>June 28, 2002</p>
		<p>
				<a href="http://download.microsoft.com/download/.netframesdk/Code/1.0/WIN98MeXP/EN-US/AsynchCalcPi.exe">Download the AsynchCalcPi.exe sample</a>.</p>
		<p>It all started innocently enough. I found myself needing to calculate the area of a circle for the first time in .NET. This called, of course, for an accurate representation of pi. System.Math.PI is handy, but since it only provides 20 digits of precision, I was worried about the accuracy of my calculation (I really needed 21 digits to be absolutely comfortable). So, like any programmer worth their salt, I forgot about the problem I was actually trying to solve and I wrote myself a program to calculate pi to any number of digits that I felt like. What I came up with is shown in Figure 1.</p>
		<p class="fig">
				<img alt="" src="http://msdn.microsoft.com/library/en-us/dnforms/html/winforms06112002-fig01.jpg" border="0" />
		</p>
		<p class="label">
				<b>Figure 1. Digits of Pi application</b>
		</p>
		<h2 class="dtH1">Progress on Long-Running Operations</h2>
		<p>While most applications don't need to calculate digits of pi, many kinds of applications need to perform long-running operations, whether it's printing, making a Web service call, or calculating interest earnings on a certain billionaire in the Pacific Northwest. Users are generally content to wait for such things, often moving to something else in the meantime, so long as they can see that progress is being made. That's why even my little application has a progress bar. The algorithm I'm using calculates pi nine digits at a time. As each new set of digits are available, my program keeps the text updated and moves the progress bar to show how we're coming along. For example, Figure 2 shows progress on the way to calculating 1000 digits of pi (if 21 digits are good, than 1000 must be better).</p>
		<p class="fig">
				<img alt="" src="http://msdn.microsoft.com/library/en-us/dnforms/html/winforms06112002-fig02.jpg" border="0" />
		</p>
		<p class="label">
				<b>Figure 2. Calculating pi to 1000 digits</b>
		</p>
		<p>The following shows how the user interface (UI) is updated as the digits of pi are calculated:</p>
		<pre class="code">
				<code>
						<b class="cfe">void ShowProgress(string pi, int totalDigits, int digitsSoFar) {
  _pi.Text = pi;
  _piProgress.Maximum = totalDigits;
  _piProgress.Value = digitsSoFar;
}

</b>
				</code>void CalcPi(int digits) {
  StringBuilder pi = new StringBuilder("3", digits + 2);

<code><b class="cfe">  // Show progress</b></code><code><b class="cfe">  ShowProgress(pi.ToString(), digits, 0);</b></code>

  if( digits &gt; 0 ) {
    pi.Append(".");

    for( int i = 0; i &lt; 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));

<code><b class="cfe">      // Show progress</b></code><code><b class="cfe">      ShowProgress(pi.ToString(), digits, i + digitCount);</b></code>
    }
  }
}
</pre>
		<p>Everything was going along fine until, in the middle of actually calculating pi to 1000 digits, I switched away to do something else and then switched back. What I saw is shown in Figure 3.</p>
		<p class="fig">
				<img alt="" src="http://msdn.microsoft.com/library/en-us/dnforms/html/winforms06112002-fig03.jpg" border="0" />
		</p>
		<p class="label">
				<b>Figure 3. No paint event for you! </b>
		</p>
		<p>The problem, of course, is that my application is single-threaded, so while the thread is calculating pi, it can't also be drawing the UI. I didn't run into this before because when I set the <b>TextBox.Text</b> and <b>ProgressBar.Value</b> properties, those controls would force their painting to happen immediately as part of setting the property (although I noticed that the progress bar was better at this than the text box). However, once I put the application into the background and then the foreground again, I need to paint the entire client area, and that's a Paint event for the form. Since no other event is going to be processed until we return from the event we're already processing (that is, the <b>Click</b> event on the Calc button), we're out of luck in terms of seeing any further progress. What I really needed to do was free the UI thread for doing UI work and handle the long-running process in the background. For this, I need another thread.</p>
		<h2 class="dtH1">Asynchronous Operations</h2>
		<p>My current synchronous <b>Click</b> handler looked like this:</p>
		<pre class="code">void _calcButton_Click(object sender, EventArgs e) {
  CalcPi((int)_digits.Value);
}
</pre>
		<p>Recall that the issue is until <b>CalcPi</b> returns, the thread can't return from our <b>Click</b> handler, which means the form can't handle the <b>Paint</b> event (or any other event, for that matter). One way to handle this is to start another thread, like so:</p>
		<pre class="code">using System.Threading;
Ã‚Â…
int _digitsToCalc = 0;

void CalcPiThreadStart() {
  CalcPi(_digitsToCalc);
}

void _calcButton_Click(object sender, EventArgs e) {
  _digitsToCalc = (int)_digits.Value;
<code><b class="cfe">  Thread piThread = new Thread(new ThreadStart(CalcPiThreadStart));            </b></code><code><b class="cfe">  piThread.Start();</b></code>
}
</pre>
		<p>Now, instead of waiting for <b>CalcPi</b> to finish before returning from the button <b>Click</b> event, I'm creating a new thread and asking it to start. The <b>Thread.Start</b> method will schedule my new thread as ready to start and then return immediately, allowing our UI thread to get back to its own work. Now, if the user wants to interact with the application (put it in the background, move it to the foreground, resize it, or even close it), the UI thread is free to handle all of those events while the worker thread calculates pi at its own pace. Figure 4 shows the two threads doing the work.</p>
		<p class="fig">
				<img alt="" src="http://msdn.microsoft.com/library/en-us/dnforms/html/winforms06112002-fig04.jpg" border="0" />
		</p>
		<p class="label">
				<b>Figure 4. Naive multithreading</b>
		</p>
		<p>You may have noticed that I'm not passing any arguments to the worker thread's entry point—<b>CalcPiThreadStart</b>. Instead, I'm tucking the number of digits to calculate into a field, <code class="ce">_digitsToCalc</code>, calling the thread entry point, which is calling <b>CalcPi</b> in turn. This is kind of a pain, which is one of the reasons that I prefer delegates for asynchronous work. Delegates support taking arguments, which saves me the hassle of an extra temporary field and an extra function between the functions I want to call.</p>
		<p>If you're not familiar with delegates, they're really just objects that call static or instance functions. In C#, they're declared using function declaration syntax. For example, a delegate to call <b>CalcPi</b> looks like this:</p>
		<pre class="code">delegate void CalcPiDelegate(int digits);
</pre>
		<p>Once I have a delegate, I can create an instance to call the <b>CalcPi</b> function synchronously like so:</p>
		<pre class="code">void _calcButton_Click(object sender, EventArgs e) {
<code><b class="cfe">  CalcPiDelegate  calcPi = new CalcPiDelegate(CalcPi);</b></code><code><b class="cfe">  calcPi((int)_digits.Value);</b></code>
}
</pre>
		<p>Of course, I don't want to call <b>CalcPi</b> synchronously; I want to call it asynchronously. Before I do that, however, we need to understand a bit more about how delegates work. My delegate declaration above declares a new class derived from <b>MultiCastDelegate</b> with three functions, <b>Invoke</b>, <b>BeginInvoke</b>, and <b>EndInvoke</b>, as shown here:</p>
		<pre class="code">class <code><b class="cfe">CalcPiDelegate</b></code> : MulticastDelegate {
<code><b class="cfe">  public void Invoke(int digits);</b></code><code><b class="cfe">  public void BeginInvoke(int digits, AsyncCallback callback,</b></code><code><b class="cfe">                          object asyncState);</b></code><code><b class="cfe">  public void EndInvoke(IAsyncResult result);</b></code>
}
</pre>
		<p>When I created an instance of the <b>CalcPiDelegate</b> earlier and then called it like a function, I was actually calling the synchronous <b>Invoke</b> function, which in turn called my own <b>CalcPi</b> function. <b>BeginInvoke</b> and <b>EndInvoke</b>, however, are the pair of functions that allow you to invoke and harvest the results of a function call asynchronously. So, to have the <b>CalcPi</b> function called on another thread, I need to call <b>BeginInvoke</b> like so:</p>
		<pre class="code">void _calcButton_Click(object sender, EventArgs e) {
  CalcPiDelegate  calcPi = new CalcPiDelegate(CalcPi);
<code><b class="cfe">  calcPi.BeginInvoke((int)_digits.Value, null, null);</b></code>
}
</pre>
		<p>Notice that we're passing nulls for the last two arguments of <b>BeginInvoke</b>. These are needed if we'd like to harvest the result from the function we're calling at some later date (which is also what <b>EndInvoke</b> is for). Since the <b>CalcPi</b> function updates the UI directly, we don't need anything but nulls for these two arguments. If you'd like the details of delegates, both synchronous and asynchronous, see <a href="http://www.sellsbrothers.com/writing/#delegates">.NET Delegates: A C# Bedtime Story</a>.</p>
		<p>At this point, I should be happy. I've got my application to combine a fully interactive UI that shows progress on a long-running operation. In fact, it wasn't until I realized what I was really doing that I became unhappy.</p>
		<h2 class="dtH1">Multithreaded Safety</h2>
		<p>As it turned out, I had just gotten lucky (or unlucky, depending on how you characterize such things). Microsoft Windows® XP was providing me with a very robust implementation of the underlying windowing system on which Windows Forms is built. So robust, in fact, that it gracefully handled my violation of the prime directive of Windows programming—<i>Though shalt not operate on a window from other than its creating thread</i>. Unfortunately there's no guarantee that other, less robust implementations of Windows would be equally graceful given my bad manners.</p>
		<p>The problem, of course, was of my own making. If you remember Figure 4, I had two threads accessing the same underlying window at the same time. However, because long-running operations are so common in Windows application, each UI class in Windows Forms (that is, every class that ultimately derives from System.Windows.Forms.Control) has a property that you can use from any thread so that you can access the window safely. The name of the property is <b>InvokeRequired</b>, which returns true if the calling thread needs to pass control over to the creating thread before calling a method on that object. A simple Assert in my <b>ShowProgress</b> function would have immediately shown me the error of my ways:</p>
		<pre class="code">using System.Diagnostics;

void ShowProgress(string pi, int totalDigits, int digitsSoFar) {
<code><b class="cfe">  // Make sure we're on the right thread</b></code><code><b class="cfe">  Debug.Assert(_pi.InvokeRequired == false);</b></code>
  ...
}
</pre>
		<p>In fact, the .NET documentation is quite clear on this point. It states, "There are four methods on a control that are safe to call from any thread: <b>Invoke</b>, <b>BeginInvoke</b>, <b>EndInvoke</b>, and <b>CreateGraphics</b>. For all other method calls, you should use one of the invoke methods to marshal the call to the control's thread." So, when I set the control properties, I'm clearly violating this rule. And from the names of the first three functions that I'm allowed to call safely (<b>Invoke</b>, <b>BeginInvoke</b>, and <b>EndInvoke</b>), it should be clear that I need to construct another delegate that will be executed in the UI thread. If I were worried about blocking my worker thread, like I was worried about blocking my UI thread, I'd need to use the asynchronous <b>BeginInvoke</b> and <b>EndInvoke</b>. However, since my worker thread exists only to service my UI thread, let's use the simpler, synchronous <b>Invoke</b> method, which is defined like this:</p>
		<pre class="code">public object Invoke(Delegate method);
public object Invoke(Delegate method, object[] args);
</pre>
		<p>The first overload of <b>Invoke</b> takes an instance of a delegate containing the method we'd like to call in the UI thread, but assumes no arguments. However, the function we want to call to update the UI, <b>ShowProgress</b>, takes three arguments, so we'll need the second overload. We'll also need another delegate for our <b>ShowProgress</b> method so that we can pass the arguments correctly. Here's how to use <b>Invoke</b> to make sure that our calls to <b>ShowProgress</b>, and therefore our use of our windows, shows up on the correct thread (making sure to replace both calls to <b>ShowProgress</b> in <b>CalcPi</b>):</p>
		<pre class="code">
				<code>
						<b class="cfe">delegate
void ShowProgressDelegate(string pi, int totalDigits, int digitsSoFar);

</b>
				</code>void CalcPi(int digits) {
  StringBuilder pi = new StringBuilder("3", digits + 2);

<code><b class="cfe">  // Get ready to show progress asynchronously</b></code><code><b class="cfe">  ShowProgressDelegate showProgress =</b></code><code><b class="cfe">    new ShowProgressDelegate(ShowProgress);</b></code><code><b class="cfe">  // Show progress</b></code><code><b class="cfe">  this.Invoke(showProgress, new object[] { pi.ToString(), digits, 0});</b></code>

  if( digits &gt; 0 ) {
    pi.Append(".");

    for( int i = 0; i &lt; digits; i += 9 ) {
      ...
<code><b class="cfe">      // Show progress</b></code><code><b class="cfe">      this.Invoke(showProgress,</b></code><code><b class="cfe">        new object[] { pi.ToString(), digits, i + digitCount});</b></code>
    }
  }
}
</pre>
		<p>The use of <b>Invoke</b> has finally given me a safe use of multithreading in my Windows Forms application. The UI thread spawns a worker thread to do the long-running operation, and the worker thread passes control back to the UI thread when the UI needs updating. Figure 5 shows our safe multithreading architecture.</p>
		<p class="fig">
				<img alt="" src="http://msdn.microsoft.com/library/en-us/dnforms/html/winforms06112002-fig05.jpg" border="0" />
		</p>
		<p class="label">
				<b>Figure 5. Safe multithreading</b>
		</p>
		<h2 class="dtH1">Simplified Multithreading</h2>
		<p>The call to <b>Invoke</b> is a bit cumbersome, and because it happens twice in our <b>CalcPi</b> function, we could simplify things and update <b>ShowProgress</b> itself to do the asynchronous call. If <b>ShowProgress</b> is called from the correct thread, it will update the controls, but if it's called from the incorrect thread, it uses <b>Invoke</b> to call itself back on the correct thread. This lets us go back to the previous, simpler <b>CalcPi</b>:</p>
		<pre class="code">void ShowProgress(string pi, int totalDigits, int digitsSoFar) {
<code><b class="cfe">  // Make sure we're on the right thread</b></code><code><b class="cfe">  if( _pi.InvokeRequired == false ) {</b></code>
    _pi.Text = pi;
    _piProgress.Maximum = totalDigits;
    _piProgress.Value = digitsSoFar;
<code><b class="cfe">  }</b></code><code><b class="cfe">  else {</b></code><code><b class="cfe">    // Show progress asynchronously</b></code><code><b class="cfe">    ShowProgressDelegate showProgress =</b></code><code><b class="cfe">      new ShowProgressDelegate(ShowProgress);</b></code><code><b class="cfe">    this.Invoke(showProgress,</b></code><code><b class="cfe">      new object[] { pi, totalDigits, digitsSoFar});</b></code><code><b class="cfe">  }</b></code>
}

void CalcPi(int digits) {
  StringBuilder pi = new StringBuilder("3", digits + 2);

<code><b class="cfe">  // Show progress</b></code><code><b class="cfe">  ShowProgress(pi.ToString(), digits, 0);</b></code>

  if( digits &gt; 0 ) {
    pi.Append(".");

    for( int i = 0; i &lt; digits; i += 9 ) {
      ...
<code><b class="cfe">      // Show progress</b></code><code><b class="cfe">      ShowProgress(pi.ToString(), digits, i + digitCount);</b></code>
    }
  }
}
</pre>
		<p>Because <b>Invoke</b> is a synchronous call and we're not consuming the return value (in fact, <b>ShowProgress</b> doesn't have a return value), it's better to use <b>BeginInvoke</b> here so that the worker thread isn't held up, as shown here:</p>
		<pre class="code">BeginInvoke(showProgress, new object[] { pi, totalDigits, digitsSoFar});
</pre>
		<p>
				<b>BeginInvoke</b> is always preferred if you don't need the return of a function call because it sends the worker thread to its work immediately and avoids the possibility of deadlock.</p>
		<h2 class="dtH1">Where Are We?</h2>
		<p>I've used this short example to demonstrate how to perform long-running operations while still showing progress and keeping the UI responsive to user interaction. To accomplish this, I used one asynch delegate to spawn a worker thread and the <b>Invoke</b> method on the main form, along with another delegate to be executed back in the UI thread.</p>
		<p>One thing I was very careful never to do was to share access to a single point of data between the UI thread and the worker thread. Instead, I passed a copy of the data needed to do the work to the worker thread (the number of digits), and a copy of the data needed to update the UI (the digits calculated so far and the progress). In the final solution, I never passed references to objects that I was sharing between the two threads, such as a reference to the current StringBuilder (which would have saved me a string copy for every time I went back to the UI thread). If I had passed shared references back and forth, I would have had to use .NET synchronization primitives to make sure to that only one thread had access to any one object at a time, which would have been a lot of work. It was already enough work just to get the calls happening between the two threads without bringing synchronization into it.</p>
		<p>Of course, if you've got large datasets that you're working with you're not going to want to copy data around. However, when possible, I recommend the combination of asynchronous delegates and message passing between the worker thread and the UI thread for implementing long-running tasks in your Windows Forms applications.</p>
		<h2 class="dtH1">Acknowledgments</h2>
		<p>I'd like to thank Simon Robinson for his post on the DevelopMentor .NET mailing list that inspired this article, Ian Griffiths for his initial work in this area, Chris Andersen for his message-passing ideas, and last but certainly not least, Mike Woodring for the fabulous multithreading pictures that I lifted shamelessly for this article.</p>
		<h2 class="dtH1">References</h2>
		<ul type="disc">
				<li>This article's source code 
</li>
				<li>
						<a href="http://www.sellsbrothers.com/writing/#delegates">.NET Delegates: A C# Bedtime Story</a>
				</li>
				<li>
						<i>Win32 Multithreaded Programming</i> by Mike Woodring and Aaron Cohen </li>
		</ul>
		<hr noshade="" size="1" />
		<p>
				<b>Chris Sells</b> is an independent consultant, specializing in distributed applications in .NET and COM, as well as an instructor for DevelopMentor. He's written several books, including <i>ATL Internals</i>, which is in the process of being updated for ATL7. He's also working on <i>Essential Windows Forms</i> for Addison-Wesley and <i>Mastering Visual Studio .NET</i> for O'Reilly. In his free time, Chris hosts the Web Services DevCon and directs the Genghis source-available project. More information about Chris, and his various projects, is available at <a href="http://www.sellsbrothers.com/">http://www.sellsbrothers.com</a>.<br /><br />from: <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnforms/html/winforms06112002.asp">http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnforms/html/winforms06112002.asp</a></p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/92207.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2007-01-07 13:49 <a href="http://www.blogjava.net/weidagang2046/articles/92207.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>通过多线程为基于.NET 的应用程序实现响应迅速的用户</title><link>http://www.blogjava.net/weidagang2046/articles/92206.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 07 Jan 2007 05:46:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/92206.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/92206.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/92206.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/92206.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/92206.html</trackback:ping><description><![CDATA[
		<p>用户不喜欢反应慢的程序。程序反应越慢，就越没有用户会喜欢它。在执行耗时较长的操作时，使用多线程是明智之举，它可以提高程序 UI 的响应速度，使得一切运行显得更为快速。在 Windows 中进行多线程编程曾经是 C++ 开发人员的专属特权，但是现在，可以使用所有兼容 Microsoft .NET 的语言来编写，其中包括 Visual Basic.NET。不过，Windows 窗体对线程的使用强加了一些重要限制。本文将对这些限制进行阐释，并说明如何利用它们来提供快速、高质量的 UI 体验，即使是程序要执行的任务本身速度就较慢。</p>
		<a name="EUB">
		</a>
		<h2>为什么选择多线程？</h2>
		<br />
		<p>多线程程序要比单线程程序更难于编写，并且不加选择地使用线程也是导致难以找到细小错误的重要原因。这就自然会引出两个问题：为什么不坚持编写单线程代码？如果必须使用多线程，如何才能避免缺陷呢？本文的大部分篇幅都是在回答第二个问题，但首先我要来解释一下为什么确实需要多线程。</p>
		<p>多线程处理可以使您能够通过确保程序“永不睡眠”从而保持 UI 的快速响应。大部分程序都有不响应用户的时候：它们正忙于为您执行某些操作以便响应进一步的请求。也许最广为人知的例子就是出现在“打开文件”对话框顶部的组合框。如果在展开该组合框时，CD-ROM驱动器里恰好有一张光盘，则计算机通常会在显示列表之前先读取光盘。这可能需要几秒钟的时间，在此期间，程序既不响应任何输入，也不允许取消该操作，尤其是在确实并不打算使用光驱的时候，这种情况会让人无法忍受。</p>
		<p>执行这种操作期间 UI 冻结的原因在于，UI 是个单线程程序，单线程不可能在等待 CD-ROM驱动器读取操作的同时处理用户输入，如图 1 所示。“打开文件”对话框会调用某些阻塞 (blocking) API 来确定 CD-ROM 的标题。阻塞 API 在未完成自己的工作之前不会返回，因此这期间它会阻止线程做其他事情。</p>
		<p>
				<img hspace="0" src="http://images.csdn.net/20060726/20060726115416.gif" align="baseline" border="0" />
		</p>
		<div style="WIDTH: 149px">
				<br />
				<p class="figureCaption">
						<b>图 1 单线程</b>
				</p>
				<div class="figureRule">
				</div>
		</div>
		<br />
		<p>在多线程下，像这样耗时较长的任务就可以在其自己的线程中运行，这些线程通常称为辅助线程。因为只有辅助线程受到阻止，所以阻塞操作不再导致用户界面冻结，如图 2 所示。应用程序的主线程可以继续处理用户的鼠标和键盘输入的同时，受阻的另一个线程将等待 CD-ROM 读取，或执行辅助线程可能做的任何操作。</p>
		<div style="WIDTH: 196px">
				<img hspace="0" src="http://images.csdn.net/20060726/20060726115459.gif" align="baseline" border="0" />
				<br />
				<p class="figureCaption">
						<b>图 2 多线程</b>
				</p>
				<div class="figureRule">
				</div>
		</div>
		<br />
		<p>其基本原则是，负责响应用户输入和保持用户界面为最新的线程（通常称为 UI 线程）不应该用于执行任何耗时较长的操作。惯常做法是，任何耗时超过 30ms 的操作都要考虑从 UI 线程中移除。这似乎有些夸张，因为 30ms 对于大多数人而言只不过是他们可以感觉到的最短的瞬间停顿，实际上该停顿略短于电影屏幕中显示的连续帧之间的间隔。</p>
		<p>如果鼠标单击和相应的 UI 提示（例如，重新绘制按钮）之间的延迟超过 30ms，那么操作与显示之间就会稍显不连贯，并因此产生如同影片断帧那样令人心烦的感觉。为了达到完全高质量的响应效果，上限必须是 30ms。另一方面，如果您确实不介意感觉稍显不连贯，但也不想因为停顿过长而激怒用户，则可按照通常用户所能容忍的限度将该间隔设为 100ms。</p>
		<p>这意味着如果想让用户界面保持响应迅速，则任何阻塞操作都应该在辅助线程中执行 — 不管是机械等待某事发生（例如，等待 CD-ROM 启动或者硬盘定位数据），还是等待来自网络的响应。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="ESC">
		</a>
		<h2>异步委托调用</h2>
		<br />
		<p>在辅助线程中运行代码的最简单方式是使用异步委托调用（所有委托都提供该功能）。委托通常是以同步方式进行调用，即，在调用委托时，只有包装方法返回后该调用才会返回。要以异步方式调用委托，请调用 BeginInvoke 方法，这样会对该方法排队以在系统线程池的线程中运行。调用线程会立即返回，而不用等待该方法完成。这比较适合于 UI 程序，因为可以用它来启动耗时较长的作业，而不会使用户界面反应变慢。</p>
		<p>例如，在以下代码中，System.Windows.Forms.MethodInvoker 类型是一个系统定义的委托，用于调用不带参数的方法。 </p>
		<pre class="codeSample">private void StartSomeWorkFromUIThread () {
    // The work we want to do is too slow for the UI
    // thread, so let's farm it out to a worker thread.

    MethodInvoker mi = new MethodInvoker(
        RunsOnWorkerThread);
    mi.BeginInvoke(null, null); // This will not block.
}

// The slow work is done here, on a thread
// from the system thread pool.
private void RunsOnWorkerThread() {
    DoSomethingSlow();
}
</pre>
		<p>如果想要传递参数，可以选择合适的系统定义的委托类型，或者自己来定义委托。MethodInvoker 委托并没有什么神奇之处。和其他委托一样，调用 BeginInvoke 会使该方法在系统线程池的线程中运行，而不会阻塞 UI 线程以便其可执行其他操作。对于以上情况，该方法不返回数据，所以启动它后就不用再去管它。如果您需要该方法返回的结果，则 BeginInvoke 的返回值很重要，并且您可能不传递空参数。然而，对于大多数 UI 应用程序而言，这种“启动后就不管”的风格是最有效的，稍后会对原因进行简要讨论。您应该注意到，BeginInvoke 将返回一个 IAsyncResult。这可以和委托的 EndInvoke 方法一起使用，以在该方法调用完毕后检索调用结果。</p>
		<p>还有其他一些可用于在另外的线程上运行方法的技术，例如，直接使用线程池 API 或者创建自己的线程。然而，对于大多数用户界面应用程序而言，有异步委托调用就足够了。采用这种技术不仅编码容易，而且还可以避免创建并非必需的线程，因为可以利用线程池中的共享线程来提高应用程序的整体性能。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="E3C">
		</a>
		<h2>线程和控件</h2>
		<br />
		<p>Windows 窗体体系结构对线程使用制定了严格的规则。如果只是编写单线程应用程序，则没必要知道这些规则，这是因为单线程的代码不可能违反这些规则。然而，一旦采用多线程，就需要理解 Windows 窗体中最重要的一条线程规则：除了极少数的例外情况，否则都不要在它的创建线程以外的线程中使用控件的任何成员。</p>
		<p>本规则的例外情况有文档说明，但这样的情况非常少。这适用于其类派生自 System.Windows.Forms.Control 的任何对象，其中几乎包括 UI 中的所有元素。所有的 UI 元素（包括表单本身）都是从 Control 类派生的对象。此外，这条规则的结果是一个被包含的控件（如，包含在一个表单中的按钮）必须与包含它控件位处于同一个线程中。也就是说，一个窗口中的所有控件属于同一个 UI 线程。实际中，大部分 Windows 窗体应用程序最终都只有一个线程，所有 UI 活动都发生在这个线程上。这个线程通常称为 UI 线程。这意味着您不能调用用户界面中任意控件上的任何方法，除非在该方法的文档说明中指出可以调用。该规则的例外情况（总有文档记录）非常少而且它们之间关系也不大。请注意，以下代码是非法的： </p>
		<pre class="codeSample">// Created on UI thread
private Label lblStatus;
...
// Doesn't run on UI thread
private void RunsOnWorkerThread() {
    DoSomethingSlow();
    lblStatus.Text = "Finished!";    // BAD!!
}
</pre>
		<p>如果您在 .NET Framework 1.0 版本中尝试运行这段代码，也许会侥幸运行成功，或者初看起来是如此。这就是多线程错误中的主要问题，即它们并不会立即显现出来。甚至当出现了一些错误时，在第一次演示程序之前一切看起来也都很正常。但不要搞错 — 我刚才显示的这段代码明显违反了规则，并且可以预见，任何抱希望于“试运行时良好，应该就没有问题”的人在即将到来的调试期是会付出沉重代价的。</p>
		<p>要注意，在明确创建线程之前会发生这样的问题。使用委托的异步调用实用程序（调用它的 BeginInvoke 方法）的任何代码都可能出现同样的问题。委托提供了一个非常吸引人的解决方案来处理 UI 应用程序中缓慢、阻塞的操作，因为这些委托能使您轻松地让此种操作运行在 UI 线程外而无需自己创建新线程。但是由于以异步委托调用方式运行的代码在一个来自线程池的线程中运行，所以它不能访问任何 UI 元素。上述限制也适用于线程池中的线程和手动创建的辅助线程。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="EGD">
		</a>
		<h2>在正确的线程中调用控件</h2>
		<br />
		<p>有关控件的限制看起来似乎对多线程编程非常不利。如果在辅助线程中运行的某个缓慢操作不对 UI 产生任何影响，用户如何知道它的进行情况呢？至少，用户如何知道工作何时完成或者是否出现错误？幸运的是，虽然此限制的存在会造成不便，但并非不可逾越。有多种方式可以从辅助线程获取消息，并将该消息传递给 UI 线程。理论上讲，可以使用低级的同步原理和池化技术来生成自己的机制，但幸运的是，因为有一个以 Control 类的 Invoke 方法形式存在的解决方案，所以不需要借助于如此低级的工作方式。</p>
		<p>Invoke 方法是 Control 类中少数几个有文档记录的线程规则例外之一：它始终可以对来自任何线程的 Control 进行 Invoke 调用。Invoke 方法本身只是简单地携带委托以及可选的参数列表，并在 UI 线程中为您调用委托，而不考虑 Invoke 调用是由哪个线程发出的。实际上，为控件获取任何方法以在正确的线程上运行非常简单。但应该注意，只有在 UI 线程当前未受到阻塞时，这种机制才有效 — 调用只有在 UI 线程准备处理用户输入时才能通过。从不阻塞 UI 线程还有另一个好理由。Invoke 方法会进行测试以了解调用线程是否就是 UI 线程。如果是，它就直接调用委托。否则，它将安排线程切换，并在 UI 线程上调用委托。无论是哪种情况，委托所包装的方法都会在 UI 线程中运行，并且只有当该方法完成时，Invoke 才会返回。</p>
		<p>Control 类也支持异步版本的 Invoke，它会立即返回并安排该方法以便在将来某一时间在 UI 线程上运行。这称为 BeginInvoke，它与异步委托调用很相似，与委托的明显区别在于，该调用以异步方式在线程池的某个线程上运行，然而在此处，它以异步方式在 UI 线程上运行。实际上，Control 的 Invoke、BeginInvoke 和 EndInvoke 方法，以及 InvokeRequired 属性都是 ISynchronizeInvoke 接口的成员。该接口可由任何需要控制其事件传递方式的类实现。</p>
		<p>由于 BeginInvoke 不容易造成死锁，所以尽可能多用该方法；而少用 Invoke 方法。因为 Invoke 是同步的，所以它会阻塞辅助线程，直到 UI 线程可用。但是如果 UI 线程正在等待辅助线程执行某操作，情况会怎样呢？应用程序会死锁。BeginInvoke 从不等待 UI 线程，因而可以避免这种情况。</p>
		<p>现在，我要回顾一下前面所展示的代码片段的合法版本。首先，必须将一个委托传递给 Control 的 BeginInvoke 方法，以便可以在 UI 线程中运行对线程敏感的代码。这意味着应该将该代码放在它自己的方法中，如<a href="http://www.msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/default.aspx?fig=true#fig3" target="_blank"><b>图 3</b></a> 所示。一旦辅助线程完成缓慢的工作后，它就会调用 Label 中的 BeginInvoke，以便在其 UI 线程上运行某段代码。通过这样，它可以更新用户界面。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="EXD">
		</a>
		<h2>包装 Control.Invoke</h2>
		<br />
		<p>虽然<a href="http://www.msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/default.aspx?fig=true#fig3" target="_blank"><b>图 3</b></a>中的代码解决了这个问题，但它相当繁琐。如果辅助线程希望在结束时提供更多的反馈信息，而不是简单地给出“Finished!”消息，则 BeginInvoke 过于复杂的使用方法会令人生畏。为了传达其他消息，例如“正在处理”、“一切顺利”等等，需要设法向 UpdateUI 函数传递一个参数。可能还需要添加一个进度栏以提高反馈能力。这么多次调用 BeginInvoke 可能导致辅助线程受该代码支配。这样不仅会造成不便，而且考虑到辅助线程与 UI 的协调性，这样设计也不好。对这些进行分析之后，我们认为包装函数可以解决这两个问题，如<a href="http://www.msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/default.aspx?fig=true#fig4" target="_blank"><b>图 4</b></a> 所示。</p>
		<p>ShowProgress 方法对将调用引向正确线程的工作进行封装。这意味着辅助线程代码不再担心需要过多关注 UI 细节，而只要定期调用 ShowProgress 即可。请注意，我定义了自己的方法，该方法违背了“必须在 UI 线程上进行调用”这一规则，因为它进而只调用不受该规则约束的其他方法。这种技术会引出一个较为常见的话题：为什么不在控件上编写公共方法呢（这些方法记录为 UI 线程规则的例外）？</p>
		<p>刚好 Control 类为这样的方法提供了一个有用的工具。如果我提供一个设计为可从任何线程调用的公共方法，则完全有可能某人会从 UI 线程调用这个方法。在这种情况下，没必要调用 BeginInvoke，因为我已经处于正确的线程中。调用 Invoke 完全是浪费时间和资源，不如直接调用适当的方法。为了避免这种情况，Control 类将公开一个称为 InvokeRequired 的属性。这是“只限 UI 线程”规则的另一个例外。它可从任何线程读取，如果调用线程是 UI 线程，则返回假，其他线程则返回真。这意味着我可以按以下方式修改包装： </p>
		<pre class="codeSample">public void ShowProgress(string msg, int percentDone) {
    if (InvokeRequired) {
        // As before
        ...
    } else {
        // We're already on the UI thread just
        // call straight through.
        UpdateUI(this, new MyProgressEvents(msg,
            PercentDone));
    }
}
</pre>
		<p>ShowProgress 现在可以记录为可从任何线程调用的公共方法。这并没有消除复杂性 — 执行 BeginInvoke 的代码依然存在，它还占有一席之地。不幸的是，没有简单的方法可以完全摆脱它。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="EOE">
		</a>
		<h2>锁定</h2>
		<br />
		<p>任何并发系统都必须面对这样的事实，即，两个线程可能同时试图使用同一块数据。有时这并不是问题 — 如果多个线程在同一时间试图读取某个对象中的某个字段，则不会有问题。然而，如果有线程想要修改该数据，就会出现问题。如果线程在读取时刚好另一个线程正在写入，则读取线程有可能会看到虚假值。如果两个线程在同一时间、在同一个位置执行写入操作，则在同步写入操作发生之后，所有从该位置读取数据的线程就有可能看到一堆垃圾数据。虽然这种行为只在特定情况下才会发生，读取操作甚至不会与写入操作发生冲突，但是数据可以是两次写入结果的混加，并保持错误结果直到下一次写入值为止。为了避免这种问题，必须采取措施来确保一次只有一个线程可以读取或写入某个对象的状态。</p>
		<p>防止这些问题出现所采用的方式是，使用运行时的锁定功能。C# 可以让您利用这些功能、通过锁定关键字来保护代码（Visual Basic 也有类似构造，称为 SyncLock）。规则是，任何想要在多个线程中调用其方法的对象在每次访问其字段时（不管是读取还是写入）都应该使用锁定构造。例如，请参见<a href="http://www.msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/default.aspx?fig=true#fig5" target="_blank"><b>图 5</b></a>。</p>
		<p>锁定构造的工作方式是：公共语言运行库 (CLR) 中的每个对象都有一个与之相关的锁，任何线程均可获得该锁，但每次只能有一个线程拥有它。如果某个线程试图获取另一个线程已经拥有的锁，那么它必须等待，直到拥有该锁的线程将锁释放为止。C# 锁定构造会获取该对象锁（如果需要，要先等待另一个线程利用它完成操作），并保留到大括号中的代码退出为止。如果执行语句运行到块结尾，该锁就会被释放，并从块中部返回，或者抛出在块中没有捕捉到的异常。</p>
		<p>请注意，MoveBy 方法中的逻辑受同样的锁语句保护。当所做的修改比简单的读取或写入更复杂时，整个过程必须由单独的锁语句保护。这也适用于对多个字段进行更新 — 在对象处于一致状态之前，一定不能释放该锁。如果该锁在更新状态的过程中释放，则其他线程也许能够获得它并看到不一致状态。如果您已经拥有一个锁，并调用一个试图获取该锁的方法，则不会导致问题出现，因为单独线程允许多次获得同一个锁。对于需要锁定以保护对字段的低级访问和对字段执行的高级操作的代码，这非常重要。MoveBy 使用 Position 属性，它们同时获得该锁。只有最外面的锁阻塞完成后，该锁才会恰当地释放。</p>
		<p>对于需要锁定的代码，必须严格进行锁定。稍有疏漏，便会功亏一篑。如果一个方法在没有获取对象锁的情况下修改状态，则其余的代码在使用它之前即使小心地锁定对象也是徒劳。同样，如果一个线程在没有事先获得锁的情况下试图读取状态，则它可能读取到不正确的值。运行时无法进行检查来确保多线程代码正常运行。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="E6E">
		</a>
		<h2>死锁</h2>
		<br />
		<p>锁是确保多线程代码正常运行的基本条件，即使它们本身也会引入新的风险。在另一个线程上运行代码的最简单方式是，使用异步委托调用（请参见<a href="http://www.msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/default.aspx?fig=true#fig6" target="_blank"><b>图 6</b></a>）。</p>
		<p>如果曾经调用过 Foo 的 CallBar 方法，则这段代码会慢慢停止运行。CallBar 方法将获得 Foo 对象上的锁，并直到 BarWork 返回后才释放它。然后，BarWork 使用异步委托调用，在某个线程池线程中调用 Foo 对象的 FooWork 方法。接下来，它会在调用委托的 EndInvoke 方法前执行一些其他操作。EndInvoke 将等待辅助线程完成，但辅助线程却被阻塞在 FooWork 中。它也试图获取 Foo 对象的锁，但锁已被 CallBar 方法持有。所以，FooWork 会等待 CallBar 释放锁，但 CallBar 也在等待 BarWork 返回。不幸的是，BarWork 将等待 FooWork 完成，所以 FooWork 必须先完成，它才能开始。结果，没有线程能够进行下去。</p>
		<p>这就是一个死锁的例子，其中有两个或更多线程都被阻塞以等待对方进行。这里的情形和标准死锁情况还是有些不同，后者通常包括两个锁。这表明如果有某个因果性（过程调用链）超出线程界限，就会发生死锁，即使只包括一个锁！Control.Invoke 是一种跨线程调用过程的方法，这是个不争的重要事实。BeginInvoke 不会遇到这样的问题，因为它并不会使因果性跨线程。实际上，它会在某个线程池线程中启动一个全新的因果性，以允许原有的那个独立进行。然而，如果保留 BeginInvoke 返回的 IAsyncResult，并用它调用 EndInvoke，则又会出现问题，因为 EndInvoke 实际上已将两个因果性合二为一。避免这种情况的最简单方法是，当持有一个对象锁时，不要等待跨线程调用完成。要确保这一点，应该避免在锁语句中调用 Invoke 或 EndInvoke。其结果是，当持有一个对象锁时，将无需等待其他线程完成某操作。要坚持这个规则，说起来容易做起来难。</p>
		<p>在检查代码的 BarWork 时，它是否在锁语句的作用域内并不明显，因为在该方法中并没有锁语句。出现这个问题的唯一原因是 BarWork 调用自 Foo.CallBar 方法的锁语句。这意味着只有确保正在调用的函数并不拥有锁时，调用 Control.Invoke 或 EndIn-voke 才是安全的。对于非私有方法而言，确保这一点并不容易，所以最佳规则是，根本不调用 Control.Invoke 和 EndInvoke。这就是为什么“启动后就不管”的编程风格更可取的原因，也是为什么 Control.BeginInvoke 解决方案通常比 Control.Invoke 解决方案好的原因。</p>
		<p>有时候除了破坏规则别无选择，这种情况下就需要仔细严格地分析。但只要可能，在持有锁时就应该避免阻塞，因为如果不这样，死锁就难以消除。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="EQF">
		</a>
		<h2>使其简单</h2>
		<br />
		<p>如何既从多线程获益最大，又不会遇到困扰并发代码的棘手错误呢？如果提高的 UI 响应速度仅仅是使程序时常崩溃，那么很难说是改善了用户体验。大部分在多线程代码中普遍存在的问题都是由要一次运行多个操作的固有复杂性导致的，这是因为大多数人更善于思考连续过程而非并发过程。通常，最好的解决方案是使事情尽可能简单。</p>
		<p>UI 代码的性质是：它从外部资源接收事件，如用户输入。它会在事件发生时对其进行处理，但却将大部分时间花在了等待事件的发生。如果可以构造辅助线程和 UI 线程之间的通信，使其适合该模型，则未必会遇到这么多问题，因为不会再有新的东西引入。我是这样使事情简单化的：将辅助线程视为另一个异步事件源。如同 Button 控件传递诸如 Click 和 MouseEnter 这样的事件，可以将辅助线程视为传递事件（如 ProgressUpdate 和 WorkComplete）的某物。只是简单地将这看作一种类比，还是真正将辅助对象封装在一个类中，并按这种方式公开适当的事件，这完全取决于您。后一种选择可能需要更多的代码，但会使用户界面代码看起来更加统一。不管哪种情况，都需要 Control.BeginInvoke 在正确的线程上传递这些事件。</p>
		<p>对于辅助线程，最简单的方式是将代码编写为正常顺序的代码块。但如果想要使用刚才介绍的“将辅助线程作为事件源”模型，那又该如何呢？这个模型非常适用，但它对该代码与用户界面的交互提出了限制：这个线程只能向 UI 发送消息，并不能向它提出请求。</p>
		<p>例如，让辅助线程中途发起对话以请求完成结果需要的信息将非常困难。如果确实需要这样做，也最好是在辅助线程中发起这样的对话，而不要在主 UI 线程中发起。该约束是有利的，因为它将确保有一个非常简单且适用于两线程间通信的模型 — 在这里简单是成功的关键。这种开发风格的优势在于，在等待另一个线程时，不会出现线程阻塞。这是避免死锁的有效策略。</p>
		<p>
				<a href="http://www.msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/default.aspx?fig=true#fig7" target="_blank">
						<b>图 7</b>
				</a> 显示了使用异步委托调用以在辅助线程中执行可能较慢的操作（读取某个目录的内容），然后将结果显示在 UI 上。它还不至于使用高级事件语法，但是该调用确实是以与处理事件（如单击）非常相似的方式来处理完整的辅助代码。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="EAG">
		</a>
		<h2>取消</h2>
		<br />
		<p>前面示例所带来的问题是，要取消操作只能通过退出整个应用程序实现。虽然在读取某个目录时 UI 仍然保持迅速响应，但由于在当前操作完成之前程序将禁用相关按钮，所以用户无法查看另一个目录。如果试图读取的目录是在一台刚好没有响应的远程机器上，这就很不幸，因为这样的操作需要很长时间才会超时。</p>
		<p>要取消一个操作也比较困难，尽管这取决于怎样才算取消。一种可能的理解是“停止等待这个操作完成，并继续另一个操作。”这实际上是抛弃进行中的操作，并忽略最终完成时可能产生的后果。对于当前示例，这是最好的选择，因为当前正在处理的操作（读取目录内容）是通过调用一个阻塞 API 来执行的，取消它没有关系。但即使是如此松散的“假取消”也需要进行大量工作。如果决定启动新的读取操作而不等待原来的操作完成，则无法知道下一个接收到的通知是来自这两个未处理请求中的哪一个。</p>
		<p>支持取消在辅助线程中运行的请求的唯一方式是，提供与每个请求相关的某种调用对象。最简单的做法是将它作为一个 Cookie，由辅助线程在每次通知时传递，允许 UI 线程将事件与请求相关联。通过简单的身份比较（参见<a href="http://www.msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/default.aspx?fig=true#fig8" target="_blank"><b>图 8</b></a>），UI 代码就可以知道事件是来自当前请求，还是来自早已废弃的请求。</p>
		<p>如果简单抛弃就行，那固然很好，不过您可能想要做得更好。如果辅助线程执行的是进行一连串阻塞操作的复杂操作，那么您可能希望辅助线程在最早的时机停止。否则，它可能会继续几分钟的无用操作。在这种情况下，调用对象需要做的就不止是作为一个被动 Cookie。它至少还需要维护一个标记，指明请求是否被取消。UI 可以随时设置这个标记，而辅助线程在执行时将定期测试这个标记，以确定是否需要放弃当前工作。</p>
		<p>对于这个方案，还需要做出几个决定：如果 UI 取消了操作，它是否要等待直到辅助线程注意到这次取消？如果不等待，就需要考虑一个争用条件：有可能 UI 线程会取消该操作，但在设置控制标记之前辅助线程已经决定传递通知了。因为 UI 线程决定不等待，直到辅助线程处理取消，所以 UI 线程有可能会继续从辅助线程接收通知。如果辅助线程使用 BeginInvoke 异步传递通知，则 UI 甚至有可能收到多个通知。UI 线程也可以始终按与“废弃”做法相同的方式处理通知 — 检查调用对象的标识并忽略它不再关心的操作通知。或者，在调用对象中进行锁定并决不从辅助线程调用 BeginInvoke 以解决问题。但由于让 UI 线程在处理一个事件之前简单地对其进行检查以确定是否有用也比较简单，所以使用该方法碰到的问题可能会更少。</p>
		<p>请查看“代码下载”（本文顶部的链接）中的 AsyncUtils，它是一个有用的基类，可为基于辅助线程的操作提供取消功能。<a href="http://www.msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/default.aspx?fig=true#fig9" target="_blank"><b>图 9</b></a> 显示了一个派生类，它实现了支持取消的递归目录搜索。这些类阐明了一些有趣的技术。它们都使用 C# 事件语法来提供通知。该基类将公开一些在操作成功完成、取消和抛出异常时出现的事件。派生类对此进行了扩充，它们将公开通知客户端搜索匹配、进度以及显示当前正在搜索哪个目录的事件。这些事件始终在 UI 线程中传递。实际上，这些类并未限制为 Control 类 — 它们可以将事件传递给实现 ISynchronizeInvoke 接口的任何类。<a href="http://www.msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/default.aspx?fig=true#fig10" target="_blank"><b>图 10</b></a> 是一个示例 Windows 窗体应用程序，它为 Search 类提供一个用户界面。它允许取消搜索并显示进度和结果。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="E6G">
		</a>
		<h2>程序关闭</h2>
		<br />
		<p>某些情况下，可以采用“启动后就不管”的异步操作，而不需要其他复杂要求来使操作可取消。然而，即使用户界面不要求取消，有可能还是需要实现这项功能以使程序可以彻底关闭。</p>
		<p>当应用程序退出时，如果由线程池创建的辅助线程还在运行，则这些线程会被终止。终止是简单粗暴的操作，因为关闭甚至会绕开任何还起作用的 Finally 块。如果异步操作执行的某些工作不应该以这种方式被打断，则必须确保在关闭之前这样的操作已经完成。此类操作可能包括对文件执行的写入操作，但由于突然中断后，文件可能被破坏。</p>
		<p>一种解决办法是创建自己的线程，而不用来自辅助线程池的线程，这样就自然会避开使用异步委托调用。这样，即使主线程关闭，应用程序也会等到您的线程退出后才终止。System.Threading.Thread 类有一个 IsBackground 属性可以控制这种行为。它默认为 false，这种情况下，CLR 会等到所有非背景线程都退出后才正常终止应用程序。然而，这会带来另一个问题，因为应用程序挂起时间可能会比您预期的长。窗口都关闭了，但进程仍在运行。这也许不是个问题。如果应用程序只是因为要进行一些清理工作才比正常情况挂起更长时间，那没问题。另一方面，如果应用程序在用户界面关闭后还挂起几分钟甚至几小时，那就不可接受了。例如，如果它仍然保持某些文件打开，则可能妨碍用户稍后重启该应用程序。</p>
		<p>最佳方法是，如果可能，通常应该编写自己的异步操作以便可以将其迅速取消，并在关闭应用程序之前等待所有未完成的操作完成。这意味着您可以继续使用异步委托，同时又能确保关闭操作彻底且及时。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="EIH">
		</a>
		<h2>错误处理</h2>
		<br />
		<p>在辅助线程中出现的错误一般可以通过触发 UI 线程中的事件来处理，这样错误处理方式就和完成及进程更新方式完全一样。因为很难在辅助线程上进行错误恢复，所以最简单的策略就是让所有错误都为致命错误。错误恢复的最佳策略是使操作完全失败，并在 UI 线程上执行重试逻辑。如果需要用户干涉来修复造成错误的问题，简单的做法是给出恰当的提示。</p>
		<p>AsyncUtils 类处理错误以及取消。如果操作抛出异常，该基类就会捕捉到，并通过 Failed 事件将异常传递给 UI。</p>
		<div style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 10px">
				<a href="http://csdnmanage.csdn.net/news/#top">
						<img height="9" alt="返回页首" src="http://csdnmanage.csdn.net/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" />
				</a>
				<a class="topOfPage" href="http://csdnmanage.csdn.net/news/#top">返回页首</a>
		</div>
		<a name="EPH">
		</a>
		<h2>小结</h2>
		<br />
		<p>谨慎地使用多线程代码可以使 UI 在执行耗时较长的任务时不会停止响应，从而显著提高应用程序的反应速度。异步委托调用是将执行速度缓慢的代码从 UI 线程迁移出来，从而避免此类间歇性无响应的最简单方式。</p>
		<p>Windows Forms Control 体系结构基本上是单线程，但它提供了实用程序以将来自辅助线程的调用封送返回至 UI 线程。处理来自辅助线程的通知（不管是成功、失败还是正在进行的指示）的最简单策略是，以对待来自常规控件的事件（如鼠标单击或键盘输入）的方式对待它们。这样可以避免在 UI 代码中引入新的问题，同时通信的单向性也不容易导致出现死锁。</p>
		<p>有时需要让 UI 向一个正在处理的操作发送消息。其中最常见的是取消一个操作。通过建立一个表示正在进行的调用的对象并维护由辅助线程定期检查的取消标记可实现这一目的。如果用户界面线程需要等待取消被认可（因为用户需要知道工作已确实终止，或者要求彻底退出程序），实现起来会有些复杂，但所提供的示例代码中包含了一个将所有复杂性封装在内的基类。派生类只需要执行一些必要的工作、周期性测试取消，以及要是因为取消请求而停止工作，就将结果通知基类。<br /><br />from: <a href="http://dotnet.csdn.net/n/20060726/92986.html">http://dotnet.csdn.net/n/20060726/92986.html</a></p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/92206.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2007-01-07 13:46 <a href="http://www.blogjava.net/weidagang2046/articles/92206.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>.Net 下信号量(Semaphore)的一种实现</title><link>http://www.blogjava.net/weidagang2046/articles/92128.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 05 Jan 2007 15:06:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/92128.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/92128.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/92128.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/92128.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/92128.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 动机												    从开始接触多线（进）程编程模型开始，学习的就是和信号量(Semaphore)相关的同步原语。不知道为什么				 .Net Framework 				里却没有相应的东东。要命的是, 我以前有很多久经考验的C++代码都是用她来实现的, 为了不使革命先烈的药白吃, 血白流, 只好自己生一个了。				 										什...&nbsp;&nbsp;<a href='http://www.blogjava.net/weidagang2046/articles/92128.html'>阅读全文</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/92128.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2007-01-05 23:06 <a href="http://www.blogjava.net/weidagang2046/articles/92128.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>体验Vs2005 beta2 测试工具 </title><link>http://www.blogjava.net/weidagang2046/articles/91594.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Wed, 03 Jan 2007 09:28:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/91594.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/91594.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/91594.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/91594.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/91594.html</trackback:ping><description><![CDATA[
		<font face="Courier New" size="2">在Vs2005中加入了单元测试工具,使用与NUnit差不多。但功能更加丰富了，而且使用更加方便，有利于项目的协调工作。而且还支持调试测试，(不知道NUnit支不支持,我是没用过。)方便我们调试出错代码。<br class="" />    Vs2005 Test tool 与NUnit 特性类对应<br />            NUnit                                                Vs2005 Tools<br class="" />     NUnit.Framework            </font>
		<font face="Courier New">
				<font size="2">
						<span style="COLOR: red">Microsoft.VisualStudio.QualityTools.UnitTesting.Framework<br class="" /></span>     TestFixtureAttribute                   </font>
		</font>
		<font face="Courier New">
				<font size="2">
						<span style="COLOR: red">TestClassAttribute<br class="" /></span>    TestFixtureSetUpAttribute            </font>
		</font>
		<font face="Courier New">
				<font size="2">
						<span style="COLOR: red">ClassInitializeAttribute<br class="" /></span>    TestFixtureTearDownAttribute       </font>
		</font>
		<font face="Courier New">
				<font size="2">
						<span style="COLOR: red">ClassCleanupAttribute<br class="" /></span>    SetUpAttribute                                <span style="COLOR: red">TestInitializeAttribute </span>  <br class="" />     TearDownAttribute                        </font>
		</font>
		<font face="Courier New">
				<font size="2">
						<span style="COLOR: red">TestCleanupAttribute<br class="" /></span>    <br />    创建一个测试工程。<img style="WIDTH: 660px; HEIGHT: 498px" height="498" hspace="5" src="http://www.cnblogs.com/images/cnblogs_com/hjf1223/testproject.jpg" width="685" align="baseline" /><br class="" />    打开UnitTest1.cs  它已经为我们生成了一个测试的Sample框架了.<br /></font>
		</font>
		<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 86.24%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; HEIGHT: 613px; BACKGROUND-COLOR: #eeeeee">
				<font face="Courier New" size="2">
						<img src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align="top" />
				</font>
				<font face="Courier New">
						<font size="2">
								<span style="COLOR: #000000">[TestClass]<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align="top" />    </span>
								<span style="COLOR: #0000ff">public</span>
								<span style="COLOR: #000000"> </span>
								<span style="COLOR: #0000ff">class</span>
						</font>
				</font>
				<font face="Courier New">
						<font size="2">
								<span style="COLOR: #000000"> UnitTest1<br /><img id="Codehighlighter1_43_959_Open_Image" onclick="this.style.display='none'; Codehighlighter1_43_959_Open_Text.style.display='none'; Codehighlighter1_43_959_Closed_Image.style.display='inline'; Codehighlighter1_43_959_Closed_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align="top" /><img id="Codehighlighter1_43_959_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_43_959_Closed_Text.style.display='none'; Codehighlighter1_43_959_Open_Image.style.display='inline'; Codehighlighter1_43_959_Open_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif" align="top" />    </span>
								<span id="Codehighlighter1_43_959_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">
										<img src="http://www.cnblogs.com/Images/dot.gif" />
								</span>
						</font>
				</font>
				<span id="Codehighlighter1_43_959_Open_Text">
						<font face="Courier New">
								<font size="2">
										<span style="COLOR: #000000">{<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        </span>
										<span style="COLOR: #0000ff">public</span>
								</font>
						</font>
						<span style="COLOR: #000000">
								<font face="Courier New" size="2"> UnitTest1()<br /><img id="Codehighlighter1_80_168_Open_Image" onclick="this.style.display='none'; Codehighlighter1_80_168_Open_Text.style.display='none'; Codehighlighter1_80_168_Closed_Image.style.display='inline'; Codehighlighter1_80_168_Closed_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_80_168_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_80_168_Closed_Text.style.display='none'; Codehighlighter1_80_168_Open_Image.style.display='inline'; Codehighlighter1_80_168_Open_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif" align="top" />      </font>
						</span>
						<span id="Codehighlighter1_80_168_Open_Text">
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #000000">{<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />            </span>
												<span style="COLOR: #008000">//</span>
										</font>
								</font>
								<span style="COLOR: #008000">
										<br />
										<font face="Courier New" size="2">
												<img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />            </font>
								</span>
								<span style="COLOR: #008000">
										<font face="Courier New" size="2">//</font>
								</span>
								<span style="COLOR: #008000">
										<font face="Courier New" size="2"> TODO: Add constructor logic here<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />            </font>
								</span>
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #008000">//<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" /></span>
												<span style="COLOR: #000000">        }</span>
										</font>
								</font>
						</span>
						<span style="COLOR: #000000">
								<br />
								<font face="Courier New" size="2">
										<img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />
										<br />
										<img id="Codehighlighter1_179_342_Open_Image" onclick="this.style.display='none'; Codehighlighter1_179_342_Open_Text.style.display='none'; Codehighlighter1_179_342_Closed_Image.style.display='inline'; Codehighlighter1_179_342_Closed_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" />
										<img id="Codehighlighter1_179_342_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_179_342_Closed_Text.style.display='none'; Codehighlighter1_179_342_Open_Image.style.display='inline'; Codehighlighter1_179_342_Open_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif" align="top" />        </font>
						</span>
						<span id="Codehighlighter1_179_342_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">
								<font face="Courier New" size="2">/**/</font>
						</span>
						<span id="Codehighlighter1_179_342_Open_Text">
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #808080">///</span>
												<span style="COLOR: #008000"> </span>
												<span style="COLOR: #808080">&lt;summary&gt;</span>
										</font>
								</font>
								<span style="COLOR: #008000">
										<br />
										<font face="Courier New" size="2">
												<img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        </font>
								</span>
								<span style="COLOR: #808080">
										<font face="Courier New" size="2">///</font>
								</span>
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #008000"> Initialize() is called once during test execution before<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        </span>
												<span style="COLOR: #808080">///</span>
										</font>
								</font>
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #008000"> test methods in this test class are executed.<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />        </span>
												<span style="COLOR: #808080">///</span>
												<span style="COLOR: #008000"> </span>
												<span style="COLOR: #808080">&lt;/summary&gt;</span>
												<span style="COLOR: #808080">
												</span>
										</font>
								</font>
						</span>
						<br />
						<font face="Courier New" size="2">
								<img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />
						</font>
						<font face="Courier New">
								<font size="2">
										<span style="COLOR: #000000">        [TestInitialize()]<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        </span>
										<span style="COLOR: #0000ff">public</span>
										<span style="COLOR: #000000"> </span>
										<span style="COLOR: #0000ff">void</span>
								</font>
						</font>
						<span style="COLOR: #000000">
								<font face="Courier New" size="2"> Initialize()<br /><img id="Codehighlighter1_411_472_Open_Image" onclick="this.style.display='none'; Codehighlighter1_411_472_Open_Text.style.display='none'; Codehighlighter1_411_472_Closed_Image.style.display='inline'; Codehighlighter1_411_472_Closed_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_411_472_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_411_472_Closed_Text.style.display='none'; Codehighlighter1_411_472_Open_Image.style.display='inline'; Codehighlighter1_411_472_Open_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif" align="top" />      </font>
						</span>
						<span id="Codehighlighter1_411_472_Open_Text">
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #000000">{<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />            </span>
												<span style="COLOR: #008000">//</span>
												<span style="COLOR: #008000">  TODO: Add test initialization code</span>
										</font>
								</font>
								<span style="COLOR: #008000">
										<br />
										<font face="Courier New" size="2">
												<img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />
										</font>
								</span>
								<span style="COLOR: #000000">
										<font face="Courier New" size="2">        }</font>
								</span>
						</span>
						<span style="COLOR: #000000">
								<br />
								<font face="Courier New" size="2">
										<img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />
										<br />
										<img id="Codehighlighter1_483_712_Open_Image" onclick="this.style.display='none'; Codehighlighter1_483_712_Open_Text.style.display='none'; Codehighlighter1_483_712_Closed_Image.style.display='inline'; Codehighlighter1_483_712_Closed_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" />
										<img id="Codehighlighter1_483_712_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_483_712_Closed_Text.style.display='none'; Codehighlighter1_483_712_Open_Image.style.display='inline'; Codehighlighter1_483_712_Open_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif" align="top" />        </font>
						</span>
						<span id="Codehighlighter1_483_712_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">
								<font face="Courier New" size="2">/**/</font>
						</span>
						<span id="Codehighlighter1_483_712_Open_Text">
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #808080">///</span>
												<span style="COLOR: #008000"> </span>
												<span style="COLOR: #808080">&lt;summary&gt;</span>
										</font>
								</font>
								<span style="COLOR: #008000">
										<br />
										<font face="Courier New" size="2">
												<img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        </font>
								</span>
								<span style="COLOR: #808080">
										<font face="Courier New" size="2">///</font>
								</span>
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #008000"> Cleanup() is called once during test execution after<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        </span>
												<span style="COLOR: #808080">///</span>
										</font>
								</font>
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #008000"> test methods in this class have executed unless the<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        </span>
												<span style="COLOR: #808080">///</span>
										</font>
								</font>
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #008000"> corresponding Initialize() call threw an exception.<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />        </span>
												<span style="COLOR: #808080">///</span>
												<span style="COLOR: #008000"> </span>
												<span style="COLOR: #808080">&lt;/summary&gt;</span>
												<span style="COLOR: #808080">
												</span>
										</font>
								</font>
						</span>
						<br />
						<font face="Courier New" size="2">
								<img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />
						</font>
						<font face="Courier New">
								<font size="2">
										<span style="COLOR: #000000">        [TestCleanup()]<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        </span>
										<span style="COLOR: #0000ff">public</span>
										<span style="COLOR: #000000"> </span>
										<span style="COLOR: #0000ff">void</span>
								</font>
						</font>
						<span style="COLOR: #000000">
								<font face="Courier New" size="2"> Cleanup()<br /><img id="Codehighlighter1_775_829_Open_Image" onclick="this.style.display='none'; Codehighlighter1_775_829_Open_Text.style.display='none'; Codehighlighter1_775_829_Closed_Image.style.display='inline'; Codehighlighter1_775_829_Closed_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_775_829_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_775_829_Closed_Text.style.display='none'; Codehighlighter1_775_829_Open_Image.style.display='inline'; Codehighlighter1_775_829_Open_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif" align="top" />      </font>
						</span>
						<span id="Codehighlighter1_775_829_Open_Text">
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #000000">{<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />            </span>
												<span style="COLOR: #008000">//</span>
												<span style="COLOR: #008000">  TODO: Add test cleanup code</span>
										</font>
								</font>
								<span style="COLOR: #008000">
										<br />
										<font face="Courier New" size="2">
												<img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />
										</font>
								</span>
								<span style="COLOR: #000000">
										<font face="Courier New" size="2">        }</font>
								</span>
						</span>
						<span style="COLOR: #000000">
								<br />
								<font face="Courier New" size="2">
										<img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />
										<br />
										<img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        [TestMethod]<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />        </font>
						</span>
						<font face="Courier New">
								<font size="2">
										<span style="COLOR: #0000ff">public</span>
										<span style="COLOR: #000000"> </span>
										<span style="COLOR: #0000ff">void</span>
								</font>
						</font>
						<span style="COLOR: #000000">
								<font face="Courier New" size="2"> TestMethod1()<br /><img id="Codehighlighter1_895_953_Open_Image" onclick="this.style.display='none'; Codehighlighter1_895_953_Open_Text.style.display='none'; Codehighlighter1_895_953_Closed_Image.style.display='inline'; Codehighlighter1_895_953_Closed_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_895_953_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_895_953_Closed_Text.style.display='none'; Codehighlighter1_895_953_Open_Image.style.display='inline'; Codehighlighter1_895_953_Open_Text.style.display='inline';" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif" align="top" />      </font>
						</span>
						<span id="Codehighlighter1_895_953_Open_Text">
								<font face="Courier New">
										<font size="2">
												<span style="COLOR: #000000">{<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />            Assert.IsTrue(</span>
												<span style="COLOR: #0000ff">true</span>
												<span style="COLOR: #000000">,</span>
												<span style="COLOR: #000000">"</span>
												<span style="COLOR: #000000">this is work</span>
												<span style="COLOR: #000000">"</span>
										</font>
								</font>
								<span style="COLOR: #000000">
										<font face="Courier New" size="2">);<br /><img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />        }</font>
								</span>
						</span>
						<span style="COLOR: #000000">
								<br />
								<font face="Courier New" size="2">
										<img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align="top" />    }</font>
						</span>
				</span>
		</div>
		<p>
				<font face="Courier New" size="2">    熟悉NUnit的一眼就会明白那些Attribute是干嘛用的了。在里面写了一句简单的断言语句。这样就是一个简单的测试实例了。<br />    接下来运行这个测试实例。找到Test菜单项,<img height="331" hspace="5" src="http://www.cnblogs.com/images/cnblogs_com/hjf1223/testmenu.jpg" width="279" align="baseline" />选择"Manage and Execute Tests。在这管理器里面可以选择在项目中已经存在的测试实例。并选择是否运行测试。</font>
		</p>
		<p>
				<font face="Courier New" size="2">
						<img style="WIDTH: 700px; HEIGHT: 388px" height="390" hspace="5" src="http://www.cnblogs.com/images/cnblogs_com/hjf1223/testmanager.jpg" width="744" align="baseline" /> <br />    单击"By Test List"右边那上工具栏按钮。就开始执行测试了。查看测试结果<br /><img style="WIDTH: 508px; HEIGHT: 193px" height="196" hspace="5" src="http://www.cnblogs.com/images/cnblogs_com/hjf1223/firstresults.jpg" width="505" align="baseline" />双击可以看到详细信息。<br />    刚才说到的还可以断点调试。很简单，只需要在测试实例设置断点，然后点击刚才的运行按扭的下拉框，可以看到有“Debug Checked tests”。这样就可以调试了。很方便！<br /><img height="186" hspace="5" src="http://www.cnblogs.com/images/cnblogs_com/hjf1223/debugTest.jpg" width="401" align="baseline" /><br />    就这么简单！<br /><br />from: </font>
				<a href="http://www.cnblogs.com/hjf1223/archive/2005/09/22/241757.aspx">
						<font face="Courier New" size="2">http://www.cnblogs.com/hjf1223/archive/2005/09/22/241757.aspx</font>
				</a>
		</p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/91594.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2007-01-03 17:28 <a href="http://www.blogjava.net/weidagang2046/articles/91594.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于VSS的多人签出与合并</title><link>http://www.blogjava.net/weidagang2046/articles/90751.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 29 Dec 2006 07:44:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/90751.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/90751.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/90751.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/90751.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/90751.html</trackback:ping><description><![CDATA[
		<p>1、 多人同时签出一个文件(Check Out Multiple Files)*</p>
		<p>　　缺省状态下，一个文件只允许一个人签出，管理员可以通过修改配置，允许多人同时签出。此时，VSS将跟踪所有签出该文件的用户。每当用户签入时，VSS都将和当前存于数据库内的最新版本进行比较，若用户修改的是同一文件的不同处，VSS将进行简单的合并（Merge），否则提示用户，并且不允许签入。用户可以通过VSS提供的Visual Merge工具，比较存放于VSS数据库中的文件和本地文件的异同，手工修改本地文件，直到认为已经可以签入时，方才执行最终签入操作。（参见合并）</p>
		<p>2、 合并(Merge)*</p>
		<p>　　在VSS中，合并可能发生在3种场合下：使用Multiple Checkout的工作方式；合并原先已经Branch了的文件；获取（Get）文件。 </p>
		<p>Multiple Checkout：若多个用户同时签出一个文件，第一个用户只要简单的签入就可以了。后续用户也可以签入，但他们的更改将需要和其他所有用户的更改合并，VSS将得到完整的更改内容（参见多人同时签出一个文件）。 <br />Branch：当被Branch的文件合并到其中一个分支时，VSS将会把在另一个分支上所做的改动合并到该分支上（参见对文件和工程的Branch/Share操作）。 <br />Merge on Get：在Multiple Checkout工作方式下，当使用Get Latest Version操作时可能引发合并操作，此时保存在VSS数据库中的内容将合并到本地文件。但如果某个文件是排他性签出的，则不会引发合并操作（参见排他性签出）。 </p>
		<p>　　在完成一个合并之后，VSS遵循如下规则： </p>
		<p>如果仍有冲突，VSS维持文件的签出状态，为了使文件能顺利签入，你必须排除这些冲突。 <br />如果你使用Merge Branches命令，将一个文件合并到一个工程中，而该工程中的对应文件已被签出，该文件将继续保持签出状态（参见对文件和工程的Branch/Share操作）。 <br />在任何其他时候，VSS将会提示你，或者在合并后自动签入，或者保持文件的签出状态以使你在更新VSS数据库中内容之前再核查一边。 </p>
		<p>　　缺省情况下，当发生冲突时，VSS将启用其Visual Merge工具。</p>
		<br />
		<p>
				<br />
		</p>
		<p>在默认情况下（VSS 6.0）<br /></p>
		<p>VSS使用过程中要遵循的是lock-modify-unlock流程而不是 copy-modify-merge流程（比如CVS），即开发人员首先将自己要修改源代码和文档从VSS服务器主备份文件上checkout到本地同时锁定服务器上的源代码和文档（multi - checkout情况除外），修改完成后checkout到服务器上同时解除服务器上文件的锁定。服务器集中控制所有的源程序和文档。</p>
		<br />
		<p> </p>在VSS 2005中，创建数据库的时候可以选择是copy-modify-merge，还是copy-modify-merge。<br /><br />from: <a href="http://www.cnblogs.com/weiweictgu/archive/2006/08/15/477332.html">http://www.cnblogs.com/weiweictgu/archive/2006/08/15/477332.html</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/90751.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-12-29 15:44 <a href="http://www.blogjava.net/weidagang2046/articles/90751.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>多线程更新主界面上的DataGrid</title><link>http://www.blogjava.net/weidagang2046/articles/90250.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Wed, 27 Dec 2006 03:20:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/90250.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/90250.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/90250.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/90250.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/90250.html</trackback:ping><description><![CDATA[
		<p>这是一个多线程更新DataGrid的例子。场景如下：目标是将DataGrid中的数据导入到数据库，由于DataGrid中的数据不是完全正确的，所以对于错误的数据要保留下来，让用户去改，改了之后再导，导了再改，改了再导直到全部导进数据库为止。</p>
		<p>基本的思路是：主GUI上有一个DataGrid，然后新开一个线程进行导入。线程导入数据后，把收集到的错误数据一次性返给主线程，然后显示在原来这个DataGrid中，提供给用户更改并再次导入。</p>
		<p>发起一个线程很容易，这里就不讲了，直接进入主题，如果更新主界面上的DataGrid。由于在 .Net中由线程A创建的 控件是不允许其他线程直接修改的。因此，其他线程需要<strong>委托线程A</strong>，把需要更新的数据给线程A，由他自己去更新。</p>
		<p>看如何实现的：</p>
		<p>  private delegate void ReBindDataGrid_Delegate(DataTable dt);</p>
		<p>  private void ReBindDataGrid(DataTable dt)<br />  {<br />   this.dgList.DataSource = dt.DefaultView;<br />   this.dgList.Refresh();<br />  }</p>
		<p>  private void import_ThreadCompleted(object sender, ThreadCompletedArgs e)<br />  {</p>
		<p>   this.lblIntro.Text += "\n执行完成！";</p>
		<p>   if(e.ErrorRows != null)<br />   {<br />    ReBindDataGrid_Delegate dt = new ReBindDataGrid_Delegate(ReBindDataGrid);<br />    this.Invoke(dt,new object[]{e.ErrorRows.Copy()});<br />   }<br />   else<br />   {<br />    this.pBar.Value = 0;<br />    this.rtxtInfo.Text += "..Over!";<br />  }<br />   this.dgList.Enabled = true;<br />}</p>
		<p>关键在于在主线程声明一个委托：private delegate void ReBindDataGrid_Delegate(DataTable dt);然后在导入线程的完成事件中，利用这个委托，执行主线程中的方法：ReBindDataGrid，同时把参数传给他。</p>
		<p>OK，这样就完成了。</p>
		<p>关于兼讲委托，只一句话，委托就是在二个不能直接相互操作的对象之间，建立一个桥梁。例如二个线程之间。<br /><br />from: <a href="http://www.wintle.cn/article.asp?id=127">http://www.wintle.cn/article.asp?id=127</a></p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/90250.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-12-27 11:20 <a href="http://www.blogjava.net/weidagang2046/articles/90250.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C#文件读写常用类介绍</title><link>http://www.blogjava.net/weidagang2046/articles/89120.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Wed, 20 Dec 2006 11:49:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/89120.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/89120.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/89120.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/89120.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/89120.html</trackback:ping><description><![CDATA[        首先要熟悉.NET中处理文件和文件夹的操作。File类和Directory类是其中最主要的两个类。了解它们将对后面功能的实现提供很大的便利。 <br />　    本节先对和文件系统相关的两个.NET类进行简要介绍。 <br />　　System.IO.File类和System.IO.FileInfo类主要提供有关文件的各种操作，在使用时需要引用System.IO命名空间。下面通过程序实例来介绍其主要属性和方法。 <br />　　(1) 文件打开方法：File.Open ()<br />　　该方法的声明如下： <br /><font color="#3d11ee">public static FileStream Open(string path,FileMode mode) <br /></font>　　下面的代码打开存放在c:\tempuploads目录下名称为newFile.txt文件，并在该文件中写入hello。 <br /><font color="#3809f7">private void OpenFile()<br />{ <br />　FileStream.TextFile=File.Open(@"c:\tempuploads\newFile.txt",FileMode.Append);<br />　byte [] Info = {(byte)'h',(byte)'e',(byte)'l',(byte)'l',(byte)'o'};<br />　TextFile.Write(Info,0,Info.Length);<br />　TextFile.Close();<br />} <br /></font>　　(2) 文件创建方法：File.Create() <br />　　该方法的声明如下： <br /><font color="#3809f7">public static FileStream Create(string path;)</font><br />　　下面的代码演示如何在c:\tempuploads下创建名为newFile.txt的文件。 <br />　　由于File.Create方法默认向所有用户授予对新文件的完全读/写访问权限，所以文件是用读/写访问权限打开的，必须关闭后才能由其他应用程序打开。为此，所以需要使用FileStream类的Close方法将所创建的文件关闭。 <br /><font color="#0909f7">private void MakeFile()<br />{ 　<br />    FileStream NewText=File.Create(@"c:\tempuploads\newFile.txt"); <br />　NewText.Close(); <br />}</font>　<br />        (3) 文件删除方法：File.Delete() <br />　　该方法声明如下： <br /><font color="#0909f7">public static void Delete(string path);</font><br />　　下面的代码演示如何删除c:\tempuploads目录下的newFile.txt文件。 <br /><font color="#3809f7">private void DeleteFile()<br />{<br />　File.Delete(@"c:\tempuploads\newFile.txt");<br />}</font><br />　　(4) 文件复制方法：File.Copy <br /><br />　　该方法声明如下： <br /><br />public static void Copy(string sourceFileName,string destFileName,bool overwrite); <br />　　下面的代码将c:\tempuploads\newFile.txt复制到c:\tempuploads\BackUp.txt。<br />　　由于Cope方法的OverWrite参数设为true，所以如果BackUp.txt文件已存在的话，将会被复制过去的文件所覆盖。 <br /><font color="#3d11ee">private void CopyFile()<br />{<br />　File.Copy(@"c:\tempuploads\newFile.txt",@"c:\tempuploads\BackUp.txt",true);<br />} <br /></font>　　(5) 文件移动方法：File.Move <br />　　该方法声明如下： <br /><font color="#3d11ee">public static void Move(string sourceFileName,string destFileName); <br /></font>　　下面的代码可以将c:\tempuploads下的BackUp.txt文件移动到c盘根目录下。<br />　　注意： <br />　　只能在同一个逻辑盘下进行文件转移。如果试图将c盘下的文件转移到d盘，将发生错误。 <br /><font color="#0000ff">private void MoveFile()<br />{<br />　File.Move(@"c:\tempuploads\BackUp.txt",@"c:\BackUp.txt");<br />}</font><br />　(6) 设置文件属性方法：File.SetAttributes<br />　　该方法声明如下： <br /><font color="#1111ee">public static void SetAttributes(string path,FileAttributes fileAttributes);</font><br />　　下面的代码可以设置文件c:\tempuploads\newFile.txt的属性为只读、隐藏。 <br /><font color="#3d11ee">private void SetFile()<br />{<br />　File.SetAttributes(@"c:\tempuploads\newFile.txt",<br />　FileAttributes.ReadOnly|FileAttributes.Hidden);<br />}</font><br />　　文件除了常用的只读和隐藏属性外，还有Archive(文件存档状态)，System(系统文件)，Temporary(临时文件)等。关于文件属性的详细情况请参看MSDN中FileAttributes的描述。 <br />　　(7) 判断文件是否存在的方法：File.Exist <br />　　该方法声明如下： <br /><font color="#3d11ee">public static bool Exists(string path); <br /></font>　　下面的代码判断是否存在c:\tempuploads\newFile.txt文件。若存在，先复制该文件，然后其删除，最后将复制的文件移动；若不存在，则先创建该文件，然后打开该文件并进行写入操作，最后将文件属性设为只读、隐藏。 <br /><font color="#1111ee">if(File.Exists(@"c:\tempuploads\newFile.txt")) //判断文件是否存在<br />{<br />　CopyFile(); //复制文件<br />　DeleteFile(); //删除文件<br />　MoveFile(); //移动文件<br />}<br />else<br />{<br />　MakeFile(); //生成文件<br />　OpenFile(); //打开文件<br />　SetFile(); //设置文件属性<br />} <br /></font>　　此外，File类对于Text文本提供了更多的支持。 <br />　　· AppendText：将文本追加到现有文件 <br />　　· CreateText：为写入文本创建或打开新文件 <br />　　· OpenText：打开现有文本文件以进行读取 <br />　　但上述方法主要对UTF-8的编码文本进行操作，从而显得不够灵活。在这里推荐读者使用下面的代码对txt文件进行操作。 <br />　　· 对txt文件进行“读”操作，示例代码如下： <br /><font color="#3d11ee">StreamReader TxtReader = new StreamReader(@"c:\tempuploads\newFile.txt",System.Text.Encoding.Default);<br />string FileContent;<br />FileContent = TxtReader.ReadEnd();<br />TxtReader.Close();</font><br />　　· 对txt文件进行“写”操作，示例代码如下： <br /><font color="#3809f7">StreamWriter = new StreamWrite(@"c:\tempuploads\newFile.txt",System.Text.Encoding.Default);<br />string FileContent;<br />TxtWriter.Write(FileContent);<br />TxtWriter.Close(); <br /></font>　　System.IO.Directory类和System.DirectoryInfo类 <br />　　主要提供关于目录的各种操作，使用时需要引用System.IO命名空间。下面通过程序实例来介绍其主要属性和方法。 <br />　　(1) 目录创建方法：Directory.CreateDirectory <br />　　该方法声明如下： <br /><font color="#0909f7">public static DirectoryInfo CreateDirectory(string path);</font><br />　　下面的代码演示在c:\tempuploads文件夹下创建名为NewDirectory的目录。 <br /><font color="#3809f7">private void MakeDirectory()<br />{<br />　Directory.CreateDirectory(@"c:\tempuploads\NewDirectoty"); <br />}</font><br />　　(2) 目录属性设置方法：DirectoryInfo.Atttributes <br />　　下面的代码设置c:\tempuploads\NewDirectory目录为只读、隐藏。与文件属性相同，目录属性也是使用FileAttributes来进行设置的。 <br /><font color="#3809f7">private void SetDirectory()<br />{<br />　DirectoryInfo NewDirInfo = new DirectoryInfo(@"c:\tempuploads\NewDirectoty");<br />　NewDirInfo.Atttributes = FileAttributes.ReadOnly|FileAttributes.Hidden;<br />} <br /></font>　　(3) 目录删除方法：Directory.Delete <br />　　该方法声明如下： <br /><font color="#3d11ee">public static void Delete(string path,bool recursive);</font><br />　　下面的代码可以将c:\tempuploads\BackUp目录删除。Delete方法的第二个参数为bool类型，它可以决定是否删除非空目录。如果该参数值为true，将删除整个目录，即使该目录下有文件或子目录；若为false，则仅当目录为空时才可删除。 <br /><font color="#3809f7">private void DeleteDirectory()<br />{<br />　Directory.Delete(@"c:\tempuploads\BackUp",true);<br />}</font><br />　　(4) 目录移动方法：Directory.Move <br />　　该方法声明如下： <br /><font color="#0909f7">public static void Move(string sourceDirName,string destDirName);</font><br />　　下面的代码将目录c:\tempuploads\NewDirectory移动到c:\tempuploads\BackUp。 <br />private void MoveDirectory()<br />{<br />　<font color="#3300ff">File.Move(@"c:\tempuploads\NewDirectory",@"c:\tempuploads\BackUp");</font><br />} <br />　　(5) 获取当前目录下的所有子目录方法：Directory.GetDirectories <br />　　该方法声明如下： <br /><font color="#3300ff">public static string[] GetDirectories(string path;); <br /></font>　　下面的代码读出c:\tempuploads\目录下的所有子目录，并将其存储到字符串数组中。 <br /><font color="#3300ff">private void GetDirectory()<br />{<br />　string [] Directorys;<br />　Directorys = Directory. GetDirectories (@"c:\tempuploads");<br />}</font><br />　　(6) 获取当前目录下的所有文件方法：Directory.GetFiles <br />　　该方法声明如下： <br /><font color="#0909f7">public static string[] GetFiles(string path;); <br /></font>　　下面的代码读出c:\tempuploads\目录下的所有文件，并将其存储到字符串数组中。 <br /><font color="#3300ff">private void GetFile()<br />{<br />　string [] Files;<br />　Files = Directory. GetFiles (@"c:\tempuploads",);<br />}</font><br />　　(7) 判断目录是否存在方法：Directory.Exist <br />　　该方法声明如下： <br /><font color="#3300ff">public static bool Exists(<br />　string path;<br />);</font><br />　　下面的代码判断是否存在c:\tempuploads\NewDirectory目录。若存在，先获取该目录下的子目录和文件，然后其移动，最后将移动后的目录删除。若不存在，则先创建该目录,然后将目录属性设为只读、隐藏<br /><font color="#3809f7">if(File.Exists(@"c:\tempuploads\NewDirectory")) <font color="#33cc33">//判断目录是否存在<br /></font>{<br />　GetDirectory(); <font color="#33cc33">//获取子目录<br /></font>　GetFile(); <font color="#48dd22">//获取文件<br /></font>　MoveDirectory();<font color="#3dee11"> //移动目录<br /></font>　DeleteDirectory(); <font color="#3dee11">//删除目录<br /></font>}<br />else<br />{<br />　MakeDirectory(); <font color="#11ee3d">//生成目录<br /></font>　SetDirectory(); <font color="#11ee3d">//设置目录属性<br /></font>} <br /></font>　　注意： <br />　　路径有3种方式，当前目录下的相对路径、当前工作盘的相对路径、绝对路径。以C:\Tmp\Book为例(假定当前工作目录为C:\Tmp)。“Book”，“\Tmp\Book”,“C:\Tmp\Book”都表示C:\Tmp\Book。 <br />　　另外，在C#中 “\”是特殊字符，要表示它的话需要使用“\\”。由于这种写法不方便，C#语言提供了@对其简化。只要在字符串前加上@即可直接使用“\”。所以上面的路径在C#中应该表示为“Book”，@“\Tmp\Book”，@“C:\Tmp\Book”。<br /><br />from: <a href="http://www.sz-accp.com.cn/xxyd/ShowArticle.asp?ArticleID=625">http://www.sz-accp.com.cn/xxyd/ShowArticle.asp?ArticleID=625</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/89120.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-12-20 19:49 <a href="http://www.blogjava.net/weidagang2046/articles/89120.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>解析.Net框架下的XML编程技术</title><link>http://www.blogjava.net/weidagang2046/articles/88074.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 15 Dec 2006 16:09:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/88074.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/88074.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/88074.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/88074.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/88074.html</trackback:ping><description><![CDATA[
		<font face="Verdana">一．前言： </font>
		<p>
				<font face="Verdana">XML是微软.Net战略的一个重要组成部分，而且它可谓是XML Web服务的基石，所以掌握.Net框架下的XML技术自然显得非常重要了。本文将指导大家如何运用C#语言完成.Net框架下的XML文档的读写操作。首先，我会向大家介绍.Net框架中与XML相关的命名空间和其中的重要类。其次，我还会给出有关的实例以使读者更进一步的了解XML文档的读写操作的具体方法。 </font>
		</p>
		<p>
				<font face="Verdana">二．XML命名空间和相关类简介： </font>
		</p>
		<p>
				<font face="Verdana">在深入进行.Net框架下的XML文档的操作之前，我想很有必要向大家介绍.Net框架中与XML技术有关的命名空间和其中一些重要的类。.Net框架为我们提供了以下一些命名空间：System.Xml、System.Xml.Schema、System.Xml.Serialization、System.Xml.Xpath以及 System.Xml.Xsl来包容和XML操作相关的类。 </font>
		</p>
		<p>
				<font face="Verdana">System.Xml命名空间包含了一些最重要的XML类，其中最主要的类是和XML文档的读写操作相关的类。这些类中包括4个与读相关的类以及2个与写相关的类。它们分别是：XmlReader、XmlTextReader、XmlValidatingReader、XmlNodeReader、XmlWriter以及 XmlTextWriter。本文将重点介绍这些类，因为它们是最基本也是最重要的类。 </font>
		</p>
		<p>
				<font face="Verdana">XmlReader类是一个虚基类，它包含了读XML文档的方法和属性。该类中的Read方法是一个基本的读XML文档的方法，它以流形式读取XML文档中的节点（Node）。另外，该类还提供了ReadString、ReadInnerXml、ReadOuterXml和ReadStartElement等更高级的读方法。除了提供读XML文档的方法外，XmlReader类还为程序员提供了MoveToAttribute、MoveToFirstAttribute、MoveToContent、MoveToFirstContent、MoveToElement以及 MoveToNextAttribute等具有导航功能的方法。在本文后面介绍的实例中，我们将运用到这些方法。 </font>
		</p>
		<p>
				<font face="Verdana">XmlTextReader、XmlNodeReader以及XmlValidatingReader等类是从XmlReader类继承过来的子类。根据它们的名称，我们可以知道其作用分别是读取文本内容、读取节点和读取XML模式（Schemas）。 </font>
		</p>
		<p>
				<font face="Verdana">XmlWriter类为程序员提供了许多写XML文档的方法，它是XmlTextWriter类的基类，我在后面的实例中会给出相关的运用方法。 </font>
		</p>
		<p>
				<font face="Verdana">XmlNode类是一个非常重要的类，它代表了XML文档中的某个节点。该节点可以是XML文档的根节点，这样它就代表整个XML文档了。它是许多很有用的类的基类，这些类包括插入节点的类、删除节点的类、替换节点的类以及在XML文档中完成导航功能的类。同时，XmlNode类还为程序员提供了获取双亲节点、子节点、最后一个子节点、节点名称以及节点类型等的属性。它的三个最主要的子类包括：XmlDocument、XmlDataDocument以及XmlDocumentFragment。XmlDocument类代表了一个XML文档，它提供了载入和保存XML文档的方法和属性。这些方法包括了Load、LoadXml和Save等。同时，它还提供了添加特性（Attributes）、说明（Comments）、空间（Spaces）、元素（Elements）和新节点（New Nodes）等XML项的功能。XmlDocumentFragment类代表了一部分XML文档，它能被用来添加到其他的XML文档中。XmlDataDocument类可以让程序员更好地完成和ADO.NET中的数据集对象之间的互操作。 </font>
		</p>
		<p>
				<font face="Verdana">除了上面介绍的System.Xml命名空间中的类外，该命名空间还包括了XmlConvert、XmlLinkedNode以及XmlNodeList等类，不过这些类不是本文介绍的重点，有兴趣的读者不妨去参考相关文档资料。 </font>
		</p>
		<p>
				<font face="Verdana">System.Xml.Schema命名空间中包含了和XML模式相关的类，这些类包括XmlSchema、XmlSchemaAll、XmlSchemaXPath以及XmlSchemaType等类。 </font>
		</p>
		<p>
				<font face="Verdana">System.Xml.Serialization命名空间中包含了和XML文档的序列化和反序列化操作相关的类，XML文档的序列化操作能将XML格式的数据转化为流格式的数据并能在网络中传输，而反序列化则完成相反的操作，即将流格式的数据还原成XML格式的数据。 </font>
		</p>
		<p>
				<font face="Verdana">System.Xml.XPath命名空间包含了XPathDocument、XPathExression、XPathNavigator以及XPathNodeIterator等类，这些类能完成XML文档的导航功能。在XPathDocument类的协助下，XPathNavigator类能完成快速的XML文档导航功能，该类为程序员提供了许多Move方法以完成导航功能。 </font>
		</p>
		<p>
				<font face="Verdana">System.Xml.Xsl命名空间中的类完成了XSLT的转换功能。 </font>
		</p>
		<p>
				<font face="Verdana">三．读XML文档的方法： </font>
		</p>
		<p>
				<font face="Verdana">在介绍完.Net框架中和XML有关的命名空间和相关类后，我接着向大家介绍和XML相关的一些操作。首先，我向大家介绍的读取XML文档的方法。在下面的实例程序中，我将运用VS.net开发工具附带的"books.xml"文件来作为示例。你可以在你的机器上搜索到该文件（或请参考附录），或者你也可以运用其他的XML文件。 </font>
		</p>
		<p>
				<font face="Verdana">首先，我们用XmlTextReader类的对象来读取该XML文档。方法很简单，就是在创建新对象的构造函数中指明XML文件的位置即可。 </font>
		</p>
		<p>
				<font face="Verdana">XmlTextReader textReader = new XmlTextReader("C:\\books.xml"); </font>
		</p>
		<p>
				<font face="Verdana">一旦新对象创建完毕，你就可以调用其Read方法来读取XML文档了。调用Read方法之后，信息被存储起来，你可以通过读取该对象的Name、BaseURI、Depth、LineNumber等属性来获取这些信息。下面我给出一个完整的实例，该实例通过简单的读取"books.xml"文件，然后将其中的信息显示在控制台中。 </font>
		</p>
		<p>
				<font face="Verdana">using System;<br />using System.Xml;  </font>
		</p>
		<p>
				<font face="Verdana">namespace ReadXml<br />{<br />    class Class1<br />    {<br />        static void Main( string[] args )<br />        {<br />            // 创建一个XmlTextReader类的对象并调用Read方法来读取文件<br />            XmlTextReader textReader  = new XmlTextReader("C:\\books.xml");<br />            textReader.Read();<br />            // 节点非空则执行循环体<br />            while ( textReader.Read() )<br />            {<br />                // 读取第一个元素<br />                textReader.MoveToElement();<br />                Console.WriteLine("XmlTextReader Properties Test");<br />                Console.WriteLine("===================");  </font>
		</p>
		<p>
				<font face="Verdana">                // 读取该元素的属性并显示在控制台中<br />                Console.WriteLine("Name:" + textReader.Name);<br />                Console.WriteLine("Base URI:" + textReader.BaseURI);<br />                Console.WriteLine("Local Name:" + textReader.LocalName);<br />                Console.WriteLine("Attribute Count:" + textReader.AttributeCount.ToString());<br />                Console.WriteLine("Depth:" + textReader.Depth.ToString());<br />                Console.WriteLine("Line Number:" + textReader.LineNumber.ToString());<br />                Console.WriteLine("Node Type:" + textReader.NodeType.ToString());<br />                Console.WriteLine("Attribute Count:" + textReader.Value.ToString());<br />            }<br />        }<br />    }<br />}<br /> </font>
		</p>
		<font face="Verdana">
				<p>
						<br />XmlTextReader类中有一个很重要的属性－NodeType，通过该属性，我们可以知道其节点的节点类型。而枚举类型XmlNodeType中包含了诸如Attribute、CDATA、Element、Comment、Document、DocumentType、Entity、ProcessInstruction以及WhiteSpace等的XML项的类型。通过与XmlNodeType中的元素的比较，我们可以获取相应节点的节点类型并对其完成相关的操作。下面我就给出一个实例，该实例读取每个节点的NodeType，并根据其节点类型显示其中的内容，同时程序还记录了XML文件中每种节点类型的数目。 </p>
				<p>using System;<br />using System.Xml;  </p>
				<p>namespace ReadXML<br />{<br />    class Class2<br />    {<br />        static void Main( string[] args )<br />        {<br />            int ws = 0;<br />            int pi = 0;<br />            int dc = 0;<br />            int cc = 0;<br />            int ac = 0;<br />            int et = 0;<br />            int el = 0;<br />            int xd = 0;  </p>
				<p>            XmlTextReader textReader  = new XmlTextReader("C:\\books.xml");  </p>
				<p>            while (textReader.Read())<br />            {<br />                XmlNodeType nType = textReader.NodeType;</p>
				<p>                // 节点类型为XmlDeclaration<br />                if (nType == XmlNodeType.XmlDeclaration)<br />                {<br />                    Console.WriteLine("Declaration:" + textReader.Name.ToString());<br />                    xd = xd + 1;<br />                }           </p>
				<p>                // 节点类型为Comment<br />                if( nType == XmlNodeType.Comment)<br />                {<br />                    Console.WriteLine("Comment:" + textReader.Name.ToString());<br />                    cc = cc + 1;<br />                }           </p>
				<p>                // 节点类型为Attribute<br />                if( nType == XmlNodeType.Attribute)<br />                {<br />                    Console.WriteLine("Attribute:" + textReader.Name.ToString());<br />                    ac = ac + 1;<br />                }  </p>
				<p>                // 节点类型为Element<br />                if ( nType == XmlNodeType.Element)<br />                {<br />                    Console.WriteLine("Element:" + textReader.Name.ToString());<br />                    el = el + 1;<br />                }  </p>
				<p>                // 节点类型为Entity<br />                if ( nType == XmlNodeType.Entity )<br />                {<br />                    Console.WriteLine("Entity:" + textReader.Name.ToString());<br />                    et = et + 1;<br />                }           </p>
				<p>                // 节点类型为Process Instruction<br />                if( nType == XmlNodeType.ProcessingInstruction )<br />                {<br />                    Console.WriteLine("Process Instruction:" + textReader.Name.ToString());<br />                    pi = pi + 1;<br />                }       </p>
				<p>                // 节点类型为DocumentType<br />                if( nType == XmlNodeType.DocumentType)<br />                {<br />                    Console.WriteLine("DocumentType:" + textReader.Name.ToString());<br />                    dc = dc + 1;<br />                }  </p>
				<p>                // 节点类型为Whitespace<br />                if ( nType == XmlNodeType.Whitespace )<br />                {<br />                    Console.WriteLine("WhiteSpace:" + textReader.Name.ToString());<br />                    ws = ws + 1;<br />                }<br />            }  </p>
				<p>            // 在控制台中显示每种类型的数目<br />            Console.WriteLine("Total Comments:" + cc.ToString());<br />            Console.WriteLine("Total Attributes:" + ac.ToString());<br />            Console.WriteLine("Total Elements:" + el.ToString());<br />            Console.WriteLine("Total Entity:" + et.ToString());<br />            Console.WriteLine("Total Process Instructions:" + pi.ToString());<br />            Console.WriteLine("Total Declaration:" + xd.ToString());<br />            Console.WriteLine("Total DocumentType:" + dc.ToString());<br />            Console.WriteLine("Total WhiteSpaces:" + ws.ToString());<br />        }<br />    }<br />}<br /> </p>
				<p>
						<br />以上，我向大家介绍了如何运用XmlTextReader类的对象来读取XML文档，并根据节点的NodeType属性来取得其节点类型信息。同时XmlReader这个基类还有XmlNodeReader和XmlValidatingReader等派生类，它们分别是用来读取XML文档的节点和模式的。限于篇幅，这里就不介绍了，读者可以参考有关资料。 </p>
				<p>四．写XML文档的方法： </p>
				<p>XmlWriter类包含了写XML文档所需的方法和属性，它是XmlTextWriter类和XmlNodeWriter类的基类。该类包含了WriteNode、WriteString、WriteAttributes、WriteStartElement以及WriteEndElement等一系列写XML文档的方法，其中有些方法是成对出现的。比如你要写入一个元素，你首先得调用WriteStartElement方法，接着写入实际内容，最后是调用WriteEndElement方法以表示结束。该类还包含了WriteState、XmlLang和XmlSpace等属性，其中WriteState属性表明了写的状态。因为XmlWriter类包含了很多写XML文档的方法，所以这里只是介绍最主要的几种。下面我们通过其子类XmlTextWriter类来说明如何写XML文档。 </p>
				<p>首先，我们要创建一个XmlTextWriter类的实例对象。该类的构造函数XmlTextWriter有三种重载形式，其参数分别为一个字符串、一个流对象和一个TextWriter对象。这里我们运用字符串的参数形式，该字符串就指明了所要创建的XML文件的位置，方法如下： </p>
				<p>XmlTextWriter textWriter = New XmlTextWriter("C:\\myXmFile.xml", null);<br /> </p>
				<p>
						<br />在创建完对象后，我们调用WriterStartDocument方法开始写XML文档，在完成写工作后，就调用WriteEndDocument结束写过程并调用Close方法将它关闭。在写的过程中，我们可以调用WriteComment方法来添加说明，通过调用WriteString方法来添加一个字符串，通过调用WriteStartElement和WriteEndElement方法对来添加一个元素，通过调用WriteStartAttribute和WriteEndAttribute方法对来添加一个属性。我们还可以通过调用WriteNode方法来添加整一个节点，其它的写的方法还包括WriteProcessingInstruction和WriteDocType等等。下面的实例就是介绍如何具体运用这些方法来完成XML文档的写工作的。 </p>
				<p>using System;<br />using System.Xml;  </p>
				<p>namespace WriteXML<br />{<br /> class Class1<br /> {<br />  static void Main( string[] args )<br />  {<br />   try<br />   {<br />    // 创建XmlTextWriter类的实例对象<br />    XmlTextWriter textWriter = new XmlTextWriter("C:\\w3sky.xml", null);<br />    textWriter.Formatting = Formatting.Indented;</p>
				<p>    // 开始写过程，调用WriteStartDocument方法<br />    textWriter.WriteStartDocument();  </p>
				<p>    // 写入说明<br />    textWriter.WriteComment("First Comment XmlTextWriter Sample Example");<br />    textWriter.WriteComment("w3sky.xml in root dir");   </p>
				<p>    //创建一个节点<br />    textWriter.WriteStartElement("Administrator");<br />    textWriter.WriteElementString("Name", "formble");<br />    textWriter.WriteElementString("site", "w3sky.com");<br />    textWriter.WriteEndElement();<br />    </p>
				<p>
						<br />    // 写文档结束，调用WriteEndDocument方法<br />    textWriter.WriteEndDocument();</p>
				<p>    // 关闭textWriter<br />    textWriter.Close();</p>
				<p>   }<br />   catch(System.Exception e)<br />   {<br />    Console.WriteLine(e.ToString());<br />   }<br />  }<br /> }<br />}</p>
				<p>
						<br /> </p>
				<p>五．运用XmlDocument类： </p>
				<p>XmlDocument类的对象代表了一个XML文档，它也是一个非常重要的XML类。该类包含了Load、LoadXml以及Save等重要的方法。其中Load方法可以从一个字符串指定的XML文件或是一个流对象、一个TextReader对象、一个XmlReader对象导入XML数据。LoadXml方法则完成从一个特定的XML文件导入XML数据的功能。它的Save方法则将XML数据保存到一个XML文件中或是一个流对象、一个TextWriter对象、一个XmlWriter对象中。 </p>
				<p>下面的程序中我们用到了XmlDocument类对象的LoadXml方法，它从一个XML文档段中读取XML数据并调用其Save方法将数据保存在一个文件中。 </p>
				<p>// 创建一个XmlDocument类的对象<br />XmlDocument doc = new XmlDocument();<br />doc.LoadXml(("&lt;Student type='regular' Section='B'&gt;&lt;Name&gt;Tommy Lex&lt;/Name&gt;&lt;/Student&gt;"));</p>
				<p>// 保存到文件中<br />doc.Save("C:\\student.xml");<br /> </p>
				<p>
						<br />这里，我们还可以通过改变Save方法中参数，将XML数据显示在控制台中，方法如下： </p>
				<p>doc.Save(Console.Out);<br /> </p>
				<p>
						<br />而在下面的程序中，我们用到了一个XmlTextReader对象，通过它我们读取"books.xml"文件中的XML数据。然后创建一个XmlDocument对象并载入XmlTextReader对象，这样XML数据就被读到XmlDocument对象中了。最后，通过该对象的Save方法将XML数据显示在控制台中。 </p>
				<p>XmlDocument doc = new XmlDocument();<br />// 创建一个XmlTextReader对象，读取XML数据<br />XmlTextReader reader = new XmlTextReader("c:\\books.xml");<br />reader.Read();</p>
				<p>// 载入XmlTextReader类的对象<br />doc.Load(reader);<br />// 将XML数据显示在控制台中<br />doc.Save(Console.Out);<br /> <br />六．总结： </p>
				<p>XML技术作为.Net的基石，其重要性自然不言而喻。.Net框架包含了五个命名空间和大量的类来支持与XML技术有关的操作。其中System.Xml是最重要的一个命名空间，其中的XmlReader类和XmlWriter类以及它们的派生类完成了XML文档的读写操作，是最基本也是最重要的类。XmlDocument类代表了XML文档，它能完成与整个XML文档相关的各类操作，同时和其相关的XmlDataDocument类也是非常重要的，值得读者的深入研究。 </p>
				<p>附录 </p>
				<p>"books.xml"文件如下： </p>
				<p>&lt;?xml version='1.0'?&gt;<br />&lt;!-- This file represents a fragment of a book store inventory database --&gt;<br />&lt;bookstore&gt;<br />  &lt;book genre="autobiography" publicationdate="1981" ISBN="1-861003-11-0"&gt;<br />    &lt;title&gt;The Autobiography of Benjamin Franklin&lt;/title&gt;<br />    &lt;author&gt;<br />      &lt;first-name&gt;Benjamin&lt;/first-name&gt;<br />      &lt;last-name&gt;Franklin&lt;/last-name&gt;<br />    &lt;/author&gt;<br />    &lt;price&gt;8.99&lt;/price&gt;<br />  &lt;/book&gt;<br />  &lt;book genre="novel" publicationdate="1967" ISBN="0-201-63361-2"&gt;<br />    &lt;title&gt;The Confidence Man&lt;/title&gt;<br />    &lt;author&gt;<br />      &lt;first-name&gt;Herman&lt;/first-name&gt;<br />      &lt;last-name&gt;Melville&lt;/last-name&gt;<br />    &lt;/author&gt;<br />    &lt;price&gt;11.99&lt;/price&gt;<br />  &lt;/book&gt;<br />  &lt;book genre="philosophy" publicationdate="1991" ISBN="1-861001-57-6"&gt;<br />    &lt;title&gt;The Gorgias&lt;/title&gt;<br />    &lt;author&gt;<br />      &lt;first-name&gt;Sidas&lt;/first-name&gt;<br />      &lt;last-name&gt;Plato&lt;/last-name&gt;<br />    &lt;/author&gt;<br />    &lt;price&gt;9.99&lt;/price&gt;<br />  &lt;/book&gt;<br />&lt;/bookstore&gt;<br /><br /><br />from: <a href="http://www.w3sky.com/147.html">http://www.w3sky.com/147.html</a></p>
		</font>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/88074.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-12-16 00:09 <a href="http://www.blogjava.net/weidagang2046/articles/88074.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>更改窗口客户区