.NET 2.0 引入的 BackgroundWorker 簡化了單任務異步,但面對「同時下載多個檔案、並行計算多筆報表」等多任務場景,開發者只能建立多個實例,管理極不便利。MultiBackgroundWorker 在單一實例內集中管理多個異步任務生命週期,保留熟悉的事件模型 (DoWork/ProgressChanged/RunWorkerCompleted),並新增 taskId 區分任務。
| 成員 | 簽名 | 說明 |
|---|---|---|
| RunWorkerAsync | void RunWorkerAsync(object taskId, object argument) | 啟動任務,taskId 必須唯一 |
| DoWork | event MultiDoWorkEventHandler DoWork | 背景執行邏輯,參數含 TaskId、Argument |
| ReportProgress | void ReportProgress(object taskId, int %, object userState) | 回報進度,觸發 ProgressChanged |
| ProgressChanged | event MultiProgressChangedEventHandler ProgressChanged | UI 執行緒安全更新介面 |
| RunWorkerCompleted | event MultiRunWorkerCompletedEventHandler RunWorkerCompleted | 任務結束 (正常/取消/異常) |
| CancelAsync | void CancelAsync(object taskId) | 請求取消指定任務 |
| TaskCanceled | bool TaskCanceled(object taskId) | 查詢任務是否已取消 |

ListView:顯示 TaskId、最大加數、進度%、工作執行緒 Id、結果。
_multiWorker = new MultiBackgroundWorker();
_multiWorker.DoWork += OnDoWork;
_multiWorker.ProgressChanged += OnProgressChanged;
_multiWorker.RunWorkerCompleted += OnRunWorkerCompleted;
private void btnStart_Click(object sender, EventArgs e)
{
int n = new Random().Next(50, 100);
string taskId = Guid.NewGuid().ToString();
AddListViewItem(taskId, n); // 先加入 UI 佔位
_multiWorker.RunWorkerAsync(taskId, n);
}
void OnDoWork(object sender, MultiDoWorkEventArgs e)
{
int n = (int)e.Argument;
object taskId = e.TaskId;
e.Result = ComputeSum(n, taskId, (MultiBackgroundWorker)sender, e);
}
long ComputeSum(int n, object taskId, MultiBackgroundWorker worker, MultiDoWorkEventArgs e)
{
long sum = 0;
for (int i = 1; i <= n; i++)
{
if (worker.TaskCanceled(taskId)) { e.Cancel = true; break; }
sum += i;
Thread.Sleep(1000); // 模擬耗時
int pct = (int)((float)i / n * 100);
worker.ReportProgress(taskId, pct, Thread.CurrentThread.ManagedThreadId);
}
return sum;
}
void OnProgressChanged(object sender, MultiProgressChangedEventArgs e)
{
UpdateListViewItem((string)e.TaskId, e.ProgressPercentage, (int)e.UserState);
}
void OnRunWorkerCompleted(object sender, MultiRunWorkerCompletedEventArgs e)
{
string taskId = (string)e.TaskId;
ListViewItem item;
if (e.Error != null) item = UpdateListViewItem(taskId, "Error");
else if (e.Cancelled) item = UpdateListViewItem(taskId, "Canceled");
else item = UpdateListViewItem(taskId, ((long)e.Result).ToString());
item.Tag = null; // 清除 taskId,防止重複取消
}
private void btnCancel_Click(object sender, EventArgs e)
{
foreach (ListViewItem lvi in listView1.SelectedItems)
if (lvi.Tag != null)
_multiWorker.CancelAsync(lvi.Tag);
btnCancel.Enabled = false;
}
private HybridDictionary _userStateToLifetime = new HybridDictionary();
// key: taskId, value: AsyncOperation
線程安全鎖定 SyncRoot,確保多執行緒並發存取安全。
public virtual void RunWorkerAsync(object taskId, object argument)
{
var asyncOp = AsyncOperationManager.CreateOperation(taskId);
lock (_userStateToLifetime.SyncRoot)
{
if (_userStateToLifetime.Contains(taskId))
throw new ArgumentException("Task ID must be unique");
_userStateToLifetime[taskId] = asyncOp;
}
_threadStart.BeginInvoke(taskId, argument, null, null);
}
AsyncOperation 綁定任務上下文 (自動捕獲 UI SynchronizationContext)。_threadStart 指向 WorkerThreadStart。
private void WorkerThreadStart(object taskId, object argument)
{
object result = null; Exception error = null; bool cancelled = false;
var asyncOp = GetAsyncOperation(taskId);
try
{
var e = new MultiDoWorkEventArgs(taskId, argument);
OnDoWork(e);
if (e.Cancel) cancelled = true;
else result = e.Result;
}
catch (Exception ex) { error = ex; }
if (!TaskCanceled(taskId)) // 未被外部取消
lock (_userStateToLifetime.SyncRoot)
_userStateToLifetime.Remove(taskId); // 結束生命週期
var ee = new MultiRunWorkerCompletedEventArgs(taskId, result, error, cancelled);
asyncOp.PostOperationCompleted(_onCompletedDelegate, ee);
}
ReportProgress → AsyncOperation.Post → ProgressChanged (UI 執行緒)。RunWorkerCompleted → AsyncOperation.PostOperationCompleted (UI 執行緒)。AsyncOperation 內部持有 SynchronizationContext,自動將委托排程至建立時的執行緒 (通常是 UI 執行緒),完美解決跨執行緒 UI 存取問題。
public bool TaskCanceled(object taskId) => _userStateToLifetime[taskId] == null;
public void CancelAsync(object taskId)
{
var asyncOp = _userStateToLifetime[taskId] as AsyncOperation;
if (asyncOp != null)
lock (_userStateToLifetime.SyncRoot)
_userStateToLifetime.Remove(taskId); // 移除即視為取消請求
}
DoWork 內部定期檢查 worker.TaskCanceled(taskId) 並設定 e.Cancel = true 實現協作式取消。