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方式来传递数据,如果因为复制这些数据对空间或时间消耗很大而必须采用共享数据的方式,那么必须保证数据的同步,往往也是最容易出现错误,如死锁问题等