我有一個情況,我必須在主執行緒(也就是UI執行緒)上運行一些長期運行的作業。我想為這個長期運行的作業顯示一個進度表,由于我不能在主執行緒上顯示它(因為它已經在忙于作業),我想在一個新執行緒中顯示它。
原則上這似乎是可行的,我可以看到我的表單顯示在新執行緒上。
我的問題是,我無法讓 Progress 類作業,以便我能夠更新顯示在 "第二個 UI 執行緒 "上的表單。我嘗試了以下代碼:
Dim oProgress As Progress(Of clsPrgObject)
Dim _thread As Thread = New Thread(Sub()
userControl.FrmPrg = New frmProgress(userControl.CancellationTokenSource, bAllowCancel)
應用程式.運行(userControl.FrmPrg)
End Sub)
_thread.SetApartmentState(ApartmentState.STA)
_thread.Start()
oProgress = New Progress(Of clsPrgObject)(Sub(runNumber)
userControl.FrmPrg.Invoke((Sub() userControl.FrmPrg.Status = runNumber))
End Sub)
DoSomeWorkOnUIThread(oProgress)
我在DoSomeWorkOnUIThread中呼叫了oProgress.Report,這也是可行的,但只有當我在后臺執行緒中執行作業,并在UI執行緒中顯示表單時(這與我實際想要做的正好相反)。
我還嘗試將下面顯示的代碼部分移到創建_thread的位置,但是當我試圖在DoSomeWorkOnUIThread中使用它時,oProgress就變成了Nothing。
oProgress = New Progress(Of clsPrgObject)(Sub(runNumber)
userControl.FrmPrg.Invoke((Sub() userControl.FrmPrg.Status = runNumber))
End Sub)
uj5u.com熱心網友回復:
這種方法有一些缺點,所以我不太推薦。最好是在UI執行緒上使用async,這樣你就有可能給你的狀態對話框一個所有者句柄。如果你在后臺執行緒上顯示這個狀態表單,你仍然可以給他們一個所有者句柄,但由于UI執行緒將被阻塞,你的狀態對話框可能也不會更新(兩個執行緒的輸入佇列會自動連接,所以如果一個被阻塞,另一個也會被阻塞),甚至可能出現死鎖。如果你的對話框沒有所有者,它可能會在后臺消失(例如切換到另一個應用程式,顯示桌面等),大多數用戶不知道如何用Ctrl-Esc在打開的視窗之間切換。一個非常骯臟的解決方法是將Form.TopMost和/或Form.ShowInTaskBar設定為true來避免這種情況--我不推薦這兩種方法。所以下面的代碼只是為了演示。我不熟悉 VB.NET 的語法,所以代碼是用 C# 寫的。
我想,你的clsPrgObject類的存在不僅僅是為了報告一個進度百分比值,它在我的代碼中的等價物是ProgressStatus類。你可以隨意地擴展它,以攜帶任意的進度資料:
內部類ProgressStatus
{
/// <摘要>
/// 初始化一個新的類的實體,只提供一個<see cref="StatusText"/> 變化。
/// </summary>
/// <param name="text">的值<see cref="StatusText"/>.</param>
內部 ProgressStatus(string text) => StatusText = text;
/// <摘要>
/// 初始化一個新的類的實體,只提供一個百分比的進度值。
/// </summary>
/// <param name="percentComplete">的值<見cref="PercentComplete"/>.</param>
內部 ProgressStatus(int percentComplete) => PercentComplete = percentComplete;
/// <摘要>
///初始化一個新的類的實體,提供一個狀態文本和一個進度值。
/// </summary>
/// <param name="text">的值<見cref="StatusText"/>.</param>
/// <param name="%Complete">的值<見cref="PercentComplete"/>.</param>
內部 ProgressStatus(string text, int percentComplete)
{
StatusText = text;
PercentComplete = percentComplete;
}
/// <摘要>
/// 一個可選的狀態文本。如果為空,則沒有變化。
//// </summary>
public string StatusText { get; set; }
/// <summary>
/// 一個可選的進度值。如果為空,則沒有變化。
/// </summary>
public int? PercentComplete { get; set; }
}
你的代碼中也有一個CancellationTokenSource,我想是為了允許從你的進度對話框中取消。所以我在ProgressDialog類中加入了這一點:
內部密封的區域類ProgressDialog : Form
{
private readonly ManualResetEvent ready;
private readonly CancellationTokenSource cts;
private IProgress<ProgressStatus> progress;
私有 ProgressDialog()
{
InitializeComponent()。
}
內部 ProgressDialog(ManualResetEvent ready, CancellationTokenSource cts)
: this()
{
this.ready = ready;
this.cts = cts;
}
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
/* 通常在一個windows表單應用程式中,你在這個階段會有一個適當的
* WindowsFormsSynchronizationContext。在VSTO下,請務必檢查它。
* 這對于Progress<T>類的正常作業至關重要。
*/
如果(!(SynchronizationContext.Current是WindowsFormsSynchronizationContext))
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext())。
progress = new Progress<ProgressStatus> (x =>
{
如果(x.StatusText != null)
statusLabel.Text = x.StatusText;
如果(x.PercentComplete.HasValue)
progressBar.Value = x.PercentComplete.Value。
});
//示意該實體已準備好使用,其他(UI)執行緒可以繼續使用
ready.Set();
}
內部 void SetProgress(ProgressStatus status)
{
progress.Report(status ? ? throw new ArgumentNullException(nameof(status)))。
//不使用Progress<T> 類的另一種方法。
//if (!IsHandleCreated)
//回傳。
//if (InvokeRequired)
//{
// Invoke(new Action<ProgressStatus>(SetProgress), status)。
//回傳。
//}
//if (status.StatusText != null)
// statusLabel.Text = status.StatusText。
// if (status.PercentComplete.HasValue)
// progressBar.Value = status.PercentComplete.Value。
}
private void CancelButton_Click(object sender, EventArgs e)
{
cts.Cancel()。
}
}
最后是CancellableProgress類,它管理著CancellationTokenSource和進度表格:
///<summary>
/// 在一個背景執行緒上顯示一個帶有狀態文本和進度值的對話框
/// 允許用戶取消一個操作。
/// </summary>
內部密封類 CancellableProgress : IDisposable
{
private CancellationTokenSource cts;
private ProgressDialog dialog;
內部 CancellableProgress()
{
cts = new CancellationTokenSource();
using (var ready = new ManualResetEvent(false))
{
var t = new Thread(() =>
{
dialog = new ProgressDialog(ready, cts);
dialog.FormClosed = (s, e) => dialog = null;
//在后臺執行緒上運行一個訊息泵。
Application.Run(dialog);
dialog = null;
});
t.SetApartmentState(ApartmentState.STA)。
t.Start()。
//等待,直到對話框設定好并顯示出來,準備接受進度變化。
ready.WaitOne()。
}
}
內部 CancellationToken CancellationToken => cts.Token;
內部 void ReportProgress(int percent) => ReportProgress(new ProgressStatus(%));
內部無效ReportProgress(string text) => ReportProgress(new ProgressStatus(text));
內部無效ReportProgress(string text, int percent) => ReportProgress(new ProgressStatus(text, percent))。
內部無效ReportProgress(ProgressStatus status)
{
dialog?.SetProgress(status)。
}
public void Dispose()
{
如果(dialog == null)
回傳。
dialog.Invoke((MethodInvoker)dialog.Close)。
dialog = null。
}
}
一個簡單的例子用法是:
private void HeavyWorkOnUIThread()
{
bool wasCancelled = false;
using (var progress = new CancellableProgress())
{
var token = progress.CancellationToken;
嘗試
{
progress.ReportProgress("Loop 1 running...")。
for (int i = 0; i < 100; i )
{
token.ThrowIfCancellationRequested()。
Thread.Sleep(10);
progress.ReportProgress(i)。
}
progress.ReportProgress("Loop 2 running...", 0)。
for (int i = 0; i < 100; i )
{
token.ThrowIfCancellationRequested()。
Thread.Sleep(10);
progress.ReportProgress(i);
}
}
catch (OperationCanceledException)
{
wasCancelled = true;
}
}
MessageBox.Show(wasCancelled ? "Cancelled." : "All done.")。
}
該代碼缺少一些引數檢查,另外,ProgressDialog可以被用戶簡單地關閉,等等。如果你決定使用這個解決方案,你可以自己解決這些問題。我還是更喜歡另一個方案。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/308339.html
標籤:
