1,System.Math.PI只有20位,現在我們寫一個可以算任意位的pi運算程序
2,所有算法在主線程中實現(單線程):問題是運行時界面無法做出相應,無法響應拖動等鍵盤鼠標操作,用戶體驗大受影響。
【NineDigitsOfPi.cs】
// NineDigitsOfPiAt.cs /* * Computation of the n'th decimal digit of pi with very little memory. * Written by Fabrice Bellard on January 8, 1997. * Ported to C# by Chris Sells on May 5, 2002. * * We use a slightly modified version of the method described by Simon * Plouffe in "On the Computation of the n'th decimal digit of various * transcendental numbers" (November 1996). We have modified the algorithm * to get a running time of O(n^2) instead of O(n^3log(n)^3). * * This program uses mostly integer arithmetic. It may be slow on some * hardwares where integer multiplications and divisons must be done * by software. */ using System; public class NineDigitsOfPi { public static int mul_mod(long a, long b, int m) { return (int)((a * b) % m); } // return the inverse of x mod y public static int inv_mod(int x, int y) { int q = 0; int u = x; int v = y; int a = 0; int c = 1; int t = 0; do { q = v/u; t = c; c = a-q*c; a = t; t = u; u = v-q*u; v = t; } while( u != 0 ); a = a%y; if( a < 0 ) a = y+a; return a; } // return (a^b) mod m public static int pow_mod(int a, int b, int m) { int r = 1; int aa = a; while( true ) { if ( (b&0x01) != 0 ) r = mul_mod(r, aa, m); b = b>>1; if( b == 0 ) break; aa = mul_mod(aa, aa, m); } return r; } // return true if n is prime public static bool is_prime(int n) { if( (n % 2) == 0 ) return false; int r = (int)(Math.Sqrt(n)); for( int i = 3; i <= r; i += 2 ) { if( (n % i) == 0 ) return false; } return true; } // return the prime number immediately after n public static int next_prime(int n) { do { n++; } while( !is_prime(n) ); return n; } public static int StartingAt(int n) { int av = 0; int vmax = 0; int N = (int)((n+20)*Math.Log(10)/Math.Log(2)); int num = 0; int den = 0; int kq = 0; int kq2 = 0; int t = 0; int v = 0; int s = 0; double sum = 0.0; for( int a = 3; a <= (2*N); a = next_prime(a) ) { vmax = (int)(Math.Log(2*N)/Math.Log(a)); av = 1; for( int i = 0; i < vmax; ++i ) av = av*a; s = 0; num = 1; den = 1; v = 0; kq = 1; kq2 = 1; for( int k = 1; k <= N; ++k ) { t = k; if( kq >= a ) { do { t = t/a; --v; } while( (t % a) == 0 ); kq = 0; } ++kq; num = mul_mod(num, t, av); t = (2*k-1); if( kq2 >= a ) { if( kq2 == a ) { do { t = t/a; ++v; } while( (t % a) == 0 ); } kq2 -= a; } den = mul_mod(den, t, av); kq2 += 2; if( v > 0 ) { t = inv_mod(den, av); t = mul_mod(t, num, av); t = mul_mod(t, k, av); for( int i = v; i < vmax; ++i ) t = mul_mod(t, a, av); s += t; if( s>=av ) s -= av; } } t = pow_mod(10, n-1, av); s = mul_mod(s, t, av); sum = (sum + (double)s/(double)av) % 1.0; } return (int)(sum * 1e9); } }
【Form1.cs】
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { } void ShowProgress(string pi,int totalDigits,int digitsSoFar) { this.textBox1.Text = pi; this.toolStripProgressBar1.Maximum = totalDigits; this.toolStripProgressBar1.Value = digitsSoFar; if (digitsSoFar == totalDigits) { this.toolStripStatusLabel1.Text = "Ready"; this.toolStripProgressBar1.Visible = false; } //強制界面更新以反映計算進度 this.Refresh(); } void CalcPi(int digits) { StringBuilder pi = new StringBuilder("3", digits + 2); ShowProgress(pi.ToString(), digits, 0); if (digits > 0) { pi.Append("."); for (int i = 0; i < digits; i += 9) { int nineDigits = NineDigitsOfPi.StartingAt(i + 1); int digitCount = Math.Min(digits - i, 9); string ds = string.Format("{0:D9}", nineDigits); pi.Append(ds.Substring(0, digitCount)); ShowProgress(pi.ToString(), digits, i + digitCount); } } } private void button1_Click(object sender, EventArgs e) { this.toolStripProgressBar1.Visible = true; this.toolStripStatusLabel1.Text = "Calculating..."; CalcPi((int)this.numericUpDown1.Value); } } }
3,同步回調(Invoke)
阻塞直到用戶界面線程處理完之後,請求的處理是通過將消息放入用戶界面線程的消息隊列並和其他任何消息一樣執行消息處理程序來完成的(在下面這個例子中,事件處理程序會調用我們的委託)。Invoke方法之接受一個Delegate參數,所以使用對象數組作爲參數傳入。
代碼:
【Form1.cs】
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Threading; namespace WindowsFormsApplication1 { public partial class Form1 : Form { delegate void CalcPiDelegate(int digits); delegate void ShowProgressDelegate(string pi, int totalDigits, int digitsSoFar); public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { } void ShowProgress(string pi,int totalDigits,int digitsSoFar) { this.textBox1.Text = pi; this.toolStripProgressBar1.Maximum = totalDigits; this.toolStripProgressBar1.Value = digitsSoFar; if (digitsSoFar == totalDigits) { this.toolStripStatusLabel1.Text = "Ready"; this.toolStripProgressBar1.Visible = false; } //強制界面更新以反映計算進度 //this.Refresh(); //這時不需要強制用戶界面刷新了 } void CalcPi(int digits) { StringBuilder pi = new StringBuilder("3", digits + 2); ShowProgressDelegate showProgress = new ShowProgressDelegate(ShowProgress); this.Invoke( showProgress, new object[]{pi.ToString(),digits,0} ); if (digits > 0) { pi.Append("."); for (int i = 0; i < digits; i += 9) { int nineDigits = NineDigitsOfPi.StartingAt(i + 1); int digitCount = Math.Min(digits - i, 9); string ds = string.Format("{0:D9}", nineDigits); pi.Append(ds.Substring(0, digitCount)); this.Invoke( showProgress, new object[] { pi.ToString(), digits, i + digitCount } ); //ShowProgress(pi.ToString(), digits, i + digitCount); } } } private void button1_Click(object sender, EventArgs e) { this.toolStripProgressBar1.Visible = true; this.toolStripStatusLabel1.Text = "Calculating..."; CalcPiDelegate calcPi = new CalcPiDelegate(CalcPi); calcPi.BeginInvoke((int)this.numericUpDown1.Value, EndCalcPi, calcPi);//第一個參數,CalcPi方法的參數,第二個擦數,CalcPi結束時調用的方法,第三個參數第二個參數方法的參數 //calcPi((int)this.numericUpDown1.Value); } void EndCalcPi(IAsyncResult result) { try { CalcPiDelegate calcPi = (CalcPiDelegate)result.AsyncState; calcPi.EndInvoke(result); } catch(Exception ex) { ShowProgress(ex.Message, 0, 0); } } } }
文檔大綱:
運行結果:
4,異步回調(BeginInvoke)
下面代碼與上例代碼的唯一區別就是用BeginInvoke調用而不是Invoke調用,區別是當Invoke調用後必須界面處理線程返回後計算才繼續進行,當中處於阻塞狀態,而BeginInvoke是調用後立即返回計算。沒有指定EndInvoke方法,是因爲這裏沒有申請資源,所以不需要釋放。這裏沒有返回值,如果有返回值的話需要使用IAsyncResult的實現。在其他工作線程的處理過程中,我們可以調用Control.EndInvoke方法來獲取結果之前不斷地檢查IsCompleted屬性是否爲true,但這種做法很麻煩(輪詢)。所以如果希望獲得調用用戶界面線程的結果 ,我建議工作線程應該使用Control.Invoke方法。
代碼:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Threading; namespace WindowsFormsApplication1 { public partial class Form1 : Form { delegate void CalcPiDelegate(int digits); delegate void ShowProgressDelegate(string pi, int totalDigits, int digitsSoFar); public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { } void ShowProgress(string pi,int totalDigits,int digitsSoFar) { this.textBox1.Text = pi; this.toolStripProgressBar1.Maximum = totalDigits; this.toolStripProgressBar1.Value = digitsSoFar; if (digitsSoFar == totalDigits) { this.toolStripStatusLabel1.Text = "Ready"; this.toolStripProgressBar1.Visible = false; } //強制界面更新以反映計算進度 //this.Refresh(); //這時不需要強制用戶界面刷新了 } void CalcPi(int digits) { StringBuilder pi = new StringBuilder("3", digits + 2); ShowProgressDelegate showProgress = new ShowProgressDelegate(ShowProgress); this.BeginInvoke(//異步回調 showProgress, new object[]{pi.ToString(),digits,0} ); if (digits > 0) { pi.Append("."); for (int i = 0; i < digits; i += 9) { int nineDigits = NineDigitsOfPi.StartingAt(i + 1); int digitCount = Math.Min(digits - i, 9); string ds = string.Format("{0:D9}", nineDigits); pi.Append(ds.Substring(0, digitCount)); this.BeginInvoke( //異步回調 showProgress, new object[] { pi.ToString(), digits, i + digitCount } ); //ShowProgress(pi.ToString(), digits, i + digitCount); } } } private void button1_Click(object sender, EventArgs e) { this.toolStripProgressBar1.Visible = true; this.toolStripStatusLabel1.Text = "Calculating..."; CalcPiDelegate calcPi = new CalcPiDelegate(CalcPi); calcPi.BeginInvoke((int)this.numericUpDown1.Value, EndCalcPi, calcPi);//第一個參數,CalcPi方法的參數,第二個擦數,CalcPi結束時調用的方法,第三個參數第二個參數方法的參數 //calcPi((int)this.numericUpDown1.Value); } void EndCalcPi(IAsyncResult result) { try { CalcPiDelegate calcPi = (CalcPiDelegate)result.AsyncState; calcPi.EndInvoke(result); } catch(Exception ex) { ShowProgress(ex.Message, 0, 0); } } } }
運行結果同上。(效率也差不了多少,反正我是沒感覺快多少)
5,簡化的多線程
利用系統BackgroundWorker組件可以更加方便的簡化程序,拖控件到Form上,設置WorkerReportsProgress屬性爲True,然後寫事件。
代碼:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Threading; namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { } void ShowProgress(string pi,int totalDigits,int digitsSoFar) { this.textBox1.Text = pi; this.toolStripProgressBar1.Maximum = totalDigits; this.toolStripProgressBar1.Value = digitsSoFar; if (digitsSoFar == totalDigits) { this.toolStripStatusLabel1.Text = "Ready"; this.toolStripProgressBar1.Visible = false; } } class CalcPiUserState { public readonly string Pi; public readonly int TotalDigits; public readonly int DigitsSoFar; public CalcPiUserState(string pi, int totalDigits, int digitsSoFar) { this.Pi = pi; this.TotalDigits = totalDigits; this.DigitsSoFar = digitsSoFar; } } void CalcPi(int digits) { StringBuilder pi = new StringBuilder("3", digits + 2); this.backgroundWorker1.ReportProgress(0, new CalcPiUserState(pi.ToString(), digits, 0)); if (digits > 0) { pi.Append("."); for (int i = 0; i < digits; i += 9) { int nineDigits = NineDigitsOfPi.StartingAt(i + 1); int digitCount = Math.Min(digits - i, 9); string ds = string.Format("{0:D9}", nineDigits); pi.Append(ds.Substring(0, digitCount)); this.backgroundWorker1.ReportProgress(0, new CalcPiUserState(pi.ToString(), digits, i + digitCount)); } } } private void button1_Click(object sender, EventArgs e) { this.toolStripProgressBar1.Visible = true; this.toolStripStatusLabel1.Text = "Calculating..."; this.backgroundWorker1.RunWorkerAsync((int)this.numericUpDown1.Value);//工作線程中啓動pi值的計算 } private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { CalcPi((int)e.Argument); } private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { CalcPiUserState progress = (CalcPiUserState)e.UserState; ShowProgress(progress.Pi, progress.TotalDigits, progress.DigitsSoFar); } } }
運行結果:同上。
6,利用BackgroundWorker控件,還可以方便的處理結束操作。順便添加一個計算用時的功能。
代碼:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Threading; namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { } void ShowProgress(string pi,int totalDigits,int digitsSoFar) { this.textBox1.Text = pi; this.toolStripProgressBar1.Maximum = totalDigits; this.toolStripProgressBar1.Value = digitsSoFar; } class CalcPiUserState { public readonly string Pi; public readonly int TotalDigits; public readonly int DigitsSoFar; public CalcPiUserState(string pi, int totalDigits, int digitsSoFar) { this.Pi = pi; this.TotalDigits = totalDigits; this.DigitsSoFar = digitsSoFar; } } void CalcPi(int digits) { StringBuilder pi = new StringBuilder("3", digits + 2); this.backgroundWorker1.ReportProgress(0, new CalcPiUserState(pi.ToString(), digits, 0)); if (digits > 0) { pi.Append("."); for (int i = 0; i < digits; i += 9) { int nineDigits = NineDigitsOfPi.StartingAt(i + 1); int digitCount = Math.Min(digits - i, 9); string ds = string.Format("{0:D9}", nineDigits); pi.Append(ds.Substring(0, digitCount)); this.backgroundWorker1.ReportProgress(0, new CalcPiUserState(pi.ToString(), digits, i + digitCount)); } } } private void button1_Click(object sender, EventArgs e) { this.toolStripProgressBar1.Visible = true; this.toolStripStatusLabel1.Text = "Calculating..."; this.backgroundWorker1.RunWorkerAsync((int)this.numericUpDown1.Value);//工作線程中啓動pi值的計算 } private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { DateTime start = DateTime.Now; CalcPi((int)e.Argument); //返回花費的時間 DateTime end = DateTime.Now; TimeSpan elapsed = end - start; e.Result = elapsed; } private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { CalcPiUserState progress = (CalcPiUserState)e.UserState; ShowProgress(progress.Pi, progress.TotalDigits, progress.DigitsSoFar); } private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { this.toolStripStatusLabel1.Text = "Ready"; // 轉移到這裏咯 this.toolStripProgressBar1.Visible = false; //顯示花費的時間 TimeSpan elapsed = (TimeSpan)e.Result; MessageBox.Show("Elapsed: " + elapsed.ToString()); } } }
7,取消操作
代碼:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Threading; namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { } void ShowProgress(string pi,int totalDigits,int digitsSoFar) { this.textBox1.Text = pi; this.toolStripProgressBar1.Maximum = totalDigits; this.toolStripProgressBar1.Value = digitsSoFar; } class CalcPiUserState { public readonly string Pi; public readonly int TotalDigits; public readonly int DigitsSoFar; public CalcPiUserState(string pi, int totalDigits, int digitsSoFar) { this.Pi = pi; this.TotalDigits = totalDigits; this.DigitsSoFar = digitsSoFar; } } void CalcPi(int digits) { StringBuilder pi = new StringBuilder("3", digits + 2); this.backgroundWorker1.ReportProgress(0, new CalcPiUserState(pi.ToString(), digits, 0)); if (digits > 0) { pi.Append("."); for (int i = 0; i < digits; i += 9) { int nineDigits = NineDigitsOfPi.StartingAt(i + 1); int digitCount = Math.Min(digits - i, 9); string ds = string.Format("{0:D9}", nineDigits); pi.Append(ds.Substring(0, digitCount)); this.backgroundWorker1.ReportProgress(0, new CalcPiUserState(pi.ToString(), digits, i + digitCount)); //檢查是否執行取消 if (this.backgroundWorker1.CancellationPending) { return; } } } } private void button1_Click(object sender, EventArgs e) { if (this.backgroundWorker1.CancellationPending) return;//如果消除操作已經提交,正在等待,就不處理 //如果工作線程正在執行,那麼就取消他 if(this.backgroundWorker1.IsBusy) { this.button1.Enabled = false; this.backgroundWorker1.CancelAsync(); return; } this.button1.Text = "Cancel"; this.toolStripProgressBar1.Visible = true; this.toolStripStatusLabel1.Text = "Calculating..."; this.backgroundWorker1.RunWorkerAsync((int)this.numericUpDown1.Value);//工作線程中啓動pi值的計算 } private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { DateTime start = DateTime.Now; CalcPi((int)e.Argument); if (this.backgroundWorker1.CancellationPending) { e.Cancel = true; } //返回花費的時間 DateTime end = DateTime.Now; TimeSpan elapsed = end - start; e.Result = elapsed; } private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { CalcPiUserState progress = (CalcPiUserState)e.UserState; ShowProgress(progress.Pi, progress.TotalDigits, progress.DigitsSoFar); } private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { this.toolStripStatusLabel1.Text = "Ready"; // 轉移到這裏咯 this.toolStripProgressBar1.Visible = false; if (e.Error != null) { this.textBox1.Text = e.Error.Message; return; } this.button1.Text = "Calculate"; this.button1.Enabled = true; //工作線程被取消了麼? if (e.Cancelled) { this.textBox1.Text = "Canceled"; return; } //顯示花費的時間 TimeSpan elapsed = (TimeSpan)e.Result; MessageBox.Show("Elapsed: " + elapsed.ToString()); } } }
8,BackgroundWorker工作流
9,共享數據
保證數據的線程安全的方法大概有這三種:
A 通過函數傳遞數據的拷貝
函數傳值。
B 傳遞數據的所有權
看這句 this.backgroundWorker1.ReportProgress(0, new CalcPiUserState(pi.ToString(), digits, 0));
主線程在創建了新的對象之後就不在對此對象擁有所有權了
C 共享數據加鎖(lock)
上面的例子,都是用AB的方式來解決多線程的同步問題,下面我們來用C的方式來處理
代碼:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Threading; namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { } void ShowProgress(string pi,int totalDigits,int digitsSoFar) { this.textBox1.Text = pi; this.toolStripProgressBar1.Maximum = totalDigits; this.toolStripProgressBar1.Value = digitsSoFar; } class SharedCalcPiUserState { public string Pi; public int TotalDigits; public int DigitsSoFar; } SharedCalcPiUserState state = new SharedCalcPiUserState();//共享數據 object stateLock = new object();//同步標誌對象 void CalcPi(int digits) { StringBuilder pi = new StringBuilder("3", digits + 2); //this.backgroundWorker1.ReportProgress(0, new CalcPiUserState(pi.ToString(), digits, 0)); lock (stateLock) //數據(大括號中的語句)加鎖 { this.state.Pi = pi.ToString(); this.state.TotalDigits = digits; this.state.DigitsSoFar = 0; } this.backgroundWorker1.ReportProgress(0); if (digits > 0) { pi.Append("."); for (int i = 0; i < digits; i += 9) { int nineDigits = NineDigitsOfPi.StartingAt(i + 1); int digitCount = Math.Min(digits - i, 9); string ds = string.Format("{0:D9}", nineDigits); pi.Append(ds.Substring(0, digitCount)); //this.backgroundWorker1.ReportProgress(0, new CalcPiUserState(pi.ToString(), digits, i + digitCount)); this.state.Pi = pi.ToString(); this.state.TotalDigits = digits; this.state.DigitsSoFar = i + digitCount; this.backgroundWorker1.ReportProgress(0); //檢查是否執行取消 if (this.backgroundWorker1.CancellationPending) { return; } } } } private void button1_Click(object sender, EventArgs e) { if (this.backgroundWorker1.CancellationPending) return;//如果消除操作已經提交,正在等待,就不處理 //如果工作線程正在執行,那麼就取消他 if(this.backgroundWorker1.IsBusy) { this.button1.Enabled = false; this.backgroundWorker1.CancelAsync(); return; } this.button1.Text = "Cancel"; this.toolStripProgressBar1.Visible = true; this.toolStripStatusLabel1.Text = "Calculating..."; this.backgroundWorker1.RunWorkerAsync((int)this.numericUpDown1.Value);//工作線程中啓動pi值的計算 } private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { DateTime start = DateTime.Now; CalcPi((int)e.Argument); if (this.backgroundWorker1.CancellationPending) { e.Cancel = true; } //返回花費的時間 DateTime end = DateTime.Now; TimeSpan elapsed = end - start; e.Result = elapsed; } private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { //CalcPiUserState progress = (CalcPiUserState)e.UserState; //ShowProgress(progress.Pi, progress.TotalDigits, progress.DigitsSoFar); lock (stateLock) { ShowProgress(this.state.Pi, this.state.TotalDigits, this.state.DigitsSoFar); } } private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { this.toolStripStatusLabel1.Text = "Ready"; // 轉移到這裏咯 this.toolStripProgressBar1.Visible = false; if (e.Error != null) { this.textBox1.Text = e.Error.Message; return; } this.button1.Text = "Calculate"; this.button1.Enabled = true; //工作線程被取消了麼? if (e.Cancelled) { this.textBox1.Text = "Canceled"; return; } //顯示花費的時間 TimeSpan elapsed = (TimeSpan)e.Result; MessageBox.Show("Elapsed: " + elapsed.ToString()); } } }
建議大家儘量採用AB方式來傳遞數據,如果因爲複製這些數據對空間或時間消耗很大而必須採用共享數據的方式,那麼必須保證數據的同步,往往也是最容易出現錯誤,如死鎖問題等