各位IT達人好
我有關於TASK傳入陣列的問題想詢問有何解決方案
我宣告了一個陣列
所有陣列也都在同一個方法裡面
但是不知道為什麼
在task外,可以正常的看到a[]這些陣列的內容
但是在task裡面,陣列的內容就完全空白,沒有任何值被帶入
想請問是有什麼步驟錯了嗎
還請各位幫忙解答,謝謝
string[] a = new string[11];
a[1] = textBox11.Text;
a[2] = textBox12.Text;
a[3] = textBox13.Text;
a[4] = textBox14.Text;
a[5] = textBox15.Text;
a[6] = textBox16.Text;
a[7] = textBox17.Text;
a[8] = textBox18.Text;
a[9] = textBox19.Text;
a[10] = textBox20.Text;
for (int i = 1; i < 10; i++)
{
if (Directory.Exists(a[i]))
{
Task[] task = new Task[11];
task[i] = Task.Factory.StartNew(() =>
{
MessageBox.Show(a[i]);
});
}
}
線上測試: https://dotnetfiddle.net/t0crNs
我測了一下這段程式。
string[] a = new string[]
{
"A", "B", "C"
};
Task[] task = new Task[3];
for (int i = 0; i < 3; i++)
{
task[i] = Task.Factory.StartNew(() =>
{
try
{
Console.WriteLine(a[i]);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
});
}
結果如下,在 Task 中出現了 Exception。
Index was outside the bounds of the array.
Index was outside the bounds of the array.
Index was outside the bounds of the array.
首先 Task.Factory.StartNew()
會開三個新的執行續
去執行 () => {}
這個匿名方法
在方法內會用到 i
這個變數
由於 i 不是匿名方法的參數或在方法內宣告的
所以會共用外面 for 迴圈的 i 變數
和 JS 的 Scope Chain 是一樣的概念
接著因為這三段程式是非同步執行
所以 for 迴圈會先跑完
接著才執行三個 Task
由於 for 已經跑完
所以三個 Task 內讀到的 i 都會是 3
因此最後才會出現索引超出陣列範圍的錯誤
要解決 i 被共用這個問題,有兩個做法
1. 在 for 迴圈內用另一個變數把 i 存起來
for (int i = 0; i < 3; i++)
{
var index = i;
task[i] = Task.Factory.StartNew(() =>
{
Console.WriteLine(a[index]);
});
}
2. 使用可傳入參數版本的 StartNew
for (int i = 0; i < 3; i++)
{
task[i] = Task.Factory.StartNew((index) =>
{
Console.WriteLine(a[(int)index]);
}, i);
}
A
C
B
我是不清楚你理解了什麼 但是.....
for (int i = 1; i < 10; i++)
{
if (Directory.Exists(a[i]))
{
//這裡的意思是你每一次都產生一個可存放11個Task 的Array
Task[] task = new Task[11];
//然後你在這裡只讓其中一個index也就是當下的配置一個 Task
//以及 這裡其實並不需要用到 Task.Factory.StartNew
//下面會繼續解釋1
task[i] = Task.Factory.StartNew(() =>
{
//關於這裡 a[i]的問題請看下方解釋2
MessageBox.Show(a[i]);
});
}
}
解釋1:
排除掉上述邏輯很詭異的情況
實際上你指需要另行宣告一個方法例如像是
public Task ShowMessageAsync(string msg) {
MessageBox.Show(msg);
return Task.CompletedTask;
}
然後把程式碼中解釋一下面那段code改成
task[i] = ShowMessageAsync(a[i]);
即可
解釋2:
雖然我是不清楚你是什麼理由會在使用 ui的情況下還得進行非同步 (webform ?)
但不太建議你在非同步的task中調用 MessageBox (webform 有這個嗎? 就算有這樣調用也不合適)
以及 其實你只要用Linq 來做的話我想應該不至於發生這個問題
整段code 其實你指需要寫這樣
// ShowMessageAsync就是上面的方法
Task.WhenAll(a.Select(text => ShowMessageAsync(text)));
// 甚至可以寫成這樣
Task.WhenAll(a.Select(ShowMessageAsync));
你可以不需要知道總共有多少要做這件事情 反正全部都要做
你也可以用async / await來取代Task.Factory.StartNew (這不是常態用法)
最後 你可以不需要自己建立一個陣列來存儲結果
Task.WhenAll 會把你帶入的所有 Task 通通都等到執行完成後才會繼續往下進行
當然別忘了要在前面加上 await 宣告這裡要等到全部運行完
附帶一提: 如果你遇到一個必須提供無參數方法的方法 ex: Task.Run
但你又有需要用的參數必須帶入
在 c# 的匿名方法其實已經能夠有效的解決方法外帶入的變數 背後理論很複雜 這裡就不加追述
如果真的有哪個情境會導致這個方法失效的話
還有另一種方法就是
Func<Task> CreateTask(string message) {
return () => MessageBox.Show(message);
}
這個做法我還沒失敗過, 到js上也一樣掛保證不會miss message XD
凱大你好
謝謝你的回應
那個messagebox是我測試用的
然後加上TASK是為了想讓他在背景運行
for的問題....後面也有想到,不知道為啥這樣做...所以就改成這樣
Task[] task = new Task[11];
for (int i = 1; i < 10; i++)
{
if (Directory.Exists(fn[i]))
{
var j = i;
task[i] = Task.Factory.StartNew((f) =>
{
a(Convert.ToString(f));
}, fn[i]);
textBox30.AppendText(Convert.ToString(NowDateTime) + " 開始" + fn[i]+ "\r\n");
}
}
因為沒寫過C#,也沒太多程式的基礎
所以寫法都拼拼湊湊起來的
像是TASK也都沒用過,試寫下去報錯還找不到問題點(在這之前用THREAD,結果發現TASK更簡便,在4.5^還有更簡單的TASK.RUN)
所以寫法才這麼奇怪
不過程式已經完成了,也能正常運行
下次有機會我會代你說的做法進去看看
謝謝你們的回答~