KB-Keep Your Code Thread-Safe!
0 |
前幾天幫同事看一個WinForm問題,明明有Primary Key限制的DataTable,卻冒出數筆PK相同的資料,Grid還會發狂似地不斷捲動。
由於資料的更新動作來自非UI Thread,我們首先懷疑的就是Threading Issue,不過該問題只出現在尖峰時刻資料量爆多的情境下,在測試台中怎麼都無法模擬出來,於是我設計了以下的實驗,證明忽略Thread-Safe Issue時的確會衍生類似的問題。
程式是這樣寫的,Form_Load時建立一個DataTable,並以Symbo欄位l為Primary Key,接著把它指定成C1FlexGrid.DataSource。按下Button時,啟動五條Thread在DataTable中塞入亂數產生的資料,並利用DataTable.Rows.Find的方式檢查該Symbol是否已有資料,決定採取新增或是更新。有兩個重點:
1) Symbol是PK,所以任何兩筆資料的Symbol不可能相同,也不可為NULL
2) DataTable.DefaultView.Sort="Symbol",所以我們看到的資料應該是依Symbol由小到大排序
private DataTable dt = new DataTable();
private string[] symbolPool = new string[100];
private void Form1_Load(object sender, System.EventArgs e)
{
//建立資料表
dt.Columns.Add("Symbol", typeof(string));
dt.Columns.Add("Price", typeof(float));
dt.Columns.Add("Quantity", typeof(int));
dt.Columns.Add("Amount", typeof(int));
//Symbol是Primary Key
dt.PrimaryKey = new DataColumn[] { dt.Columns["Symbol"] };
//建立代號清單
for (int i = 0; i < symbolPool.Length; i++)
symbolPool[i] = i.ToString("0000");
//依代號排序
dt.DefaultView.Sort = "Symbol";
c1.DataSource = dt;
}
private void btnGo_Click(object sender, System.EventArgs e)
{
if (btnGo.Text=="GO")
{
bStop=false;
//開啟五條Thread同時塞資料
for (int i=0; i<5; i++)
{
System.Threading.ThreadPool.QueueUserWorkItem(
new WaitCallback(updateTrade));
}
btnGo.Text="Cancel";
}
else
{
bStop = true;
btnGo.Text = "GO";
}
}
//利用bStop旗標要求停止測試
private bool bStop = false;
private void updateTrade(object args)
{
Random rnd = new Random();
while (!bStop)
{
//用亂數產生測試資料
string symbol = rnd.Next(100).ToString("0000");
float prz = Convert.ToSingle(
Math.Round(rnd.NextDouble()*100, 2));
int qty = rnd.Next(50) * 1000;
int amt = Convert.ToInt32(prz * qty);
try
{
//先找看看是否已有該筆資料
DataRow row = dt.Rows.Find(symbol);
bool bNew = false;
//沒有時則準備新增
if (row==null)
{
row = dt.NewRow();
bNew = true;
}
row["Symbol"]=symbol;
row["Price"]=prz;
row["Quantity"]=qty;
row["Amount"]=amt;
if (bNew)
dt.Rows.Add(row);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
//等待不定長度的時間
Thread.Sleep(rnd.Next(200));
}
}
在.NET 1.1下執行程式,我們得到了這樣的結果
0024, 0030出現兩筆,0066後方排了一筆0030,再下方有一筆完全沒數字的資料。PK重複、PK=NULL、排序反覆,這... 程式造反了嗎? 是的,當程式涉及了多條Thread時,只要稍不留神,就會爆出各式各樣想都想不到的結果。程式再跑一段時間,也許還有機會遇見曾讓我灰頭土臉的Null Reference Exception。
在Multithread環境下使用沒有非Thread-Safe的Class/Method,產生的結果無法預期,而這也不是元件開發者的責任。當使用多個Thread去存取同一個物件時,切記要考慮彼此間的交互影響,而在上面的例子中,我們只需要加一個lock(this)就可解決問題。強制同步化會影響效能,但跑出錯誤的結果,再怎麼快也是快心酸的,二者輕重,不言而喻。
lock (this)
{
try
{
DataRow row = dt.Rows.Find(symbol);
bool bNew = false;
if (row==null)
{
row = dt.NewRow();
bNew = true;
}
row["Symbol"]=symbol;
row["Price"]=prz;
row["Quantity"]=qty;
row["Amount"]=amt;
if (bNew)
dt.Rows.Add(row);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
享受Multithread Coding樂趣之餘,千萬記住: Keep Your Code Thread-Safe!
延伸閱讀: UI Thread Issue
Comments
Be the first to post a comment