C#线程和Windows窗体
我使用后台进程实现响应GUI的方法正确吗?如果没有,请批评并提出改进。特别是,指出哪些代码可能会遇到死锁或争用情况 工作线程需要能够被取消并报告其进度。我没有使用BackgroundWorker,因为我看到的所有示例都在表单本身上有流程代码,而不是单独的对象。我曾考虑为BackgroundWorker继承LongRunningProcess,但我认为这会在对象上引入不必要的方法。理想情况下,我不希望有一个对流程的表单引用(“lrp”),但我不知道如何取消流程,除非lrp上有一个事件检查调用方的标志,但这似乎不必要地复杂,甚至可能是错误的 Windows窗体(编辑:已将*.EndInvoke调用移动到回调)C#线程和Windows窗体,c#,multithreading,concurrency,C#,Multithreading,Concurrency,我使用后台进程实现响应GUI的方法正确吗?如果没有,请批评并提出改进。特别是,指出哪些代码可能会遇到死锁或争用情况 工作线程需要能够被取消并报告其进度。我没有使用BackgroundWorker,因为我看到的所有示例都在表单本身上有流程代码,而不是单独的对象。我曾考虑为BackgroundWorker继承LongRunningProcess,但我认为这会在对象上引入不必要的方法。理想情况下,我不希望有一个对流程的表单引用(“lrp”),但我不知道如何取消流程,除非lrp上有一个事件检查调用方的标
public分部类MainForm:Form
{
MethodInvoker_startInvoker=null;
MethodInvoker _stopInvoker=null;
bool _start=false;
LongRunningProcess _lrp=null;
私有void btnAction_单击(对象发送者,事件参数e)
{
//此按钮用作启动/停止开关。
//省略GUI处理(更改按钮文本等)
如果(!\u已启动)
{
_开始=真;
var lrp=新的LongRunningProcess();
_startInvoker=newmethodinvoker((Action)(()=>Start(lrp));
_startInvoker.BeginInvoke(新的异步回调(已转移),null);
}
其他的
{
_开始=错误;
_stopInvoker=newMethodInvoker(停止);
_stopInvoker.BeginInvoke(已停止,null);
}
}
专用作废启动(LongRunningProcess lrp)
{
//存储对进程的引用
_lrp=lrp;
//这与BackgroundWorker使用的技术相同
//长时间运行的进程在运行时调用此事件
//报告其进展情况
_lrp.ProgressChanged+=新的ProgressChangedEventHandler(\u lrp\u ProgressChanged);
_lrp.RunProcess();
}
私人停车场()
{
//设置此标志后,LRP将停止处理
_lrp.CancellationPending=true;
}
//此方法在流程完成时调用
已转移私有无效(IAsyncResult asyncResult)
{
if(this.invokererequired)
{
this.BeginInvoke(新操作(已转移),异步结果);
}
其他的
{
_EndInvoke(异步结果);
_开始=错误;
_lrp=null;
}
}
私有无效已停止(IAsyncResult asyncResult)
{
if(this.invokererequired)
{
this.BeginInvoke(新操作(已停止),asyncResult);
}
其他的
{
_EndInvoke(异步结果);
_lrp=null;
}
}
私有void\u lrp\u ProgressChanged(对象发送方,ProgressChangedEventArgs e)
{
//更新进度
//如果(progressBar.invokererequired)等。。。
}
}
后台进程:
public class LongRunningProcess
{
SendOrPostCallback _progressReporter;
private readonly object _syncObject = new object();
private bool _cancellationPending = false;
public event ProgressChangedEventHandler ProgressChanged;
public bool CancellationPending
{
get { lock (_syncObject) { return _cancellationPending; } }
set { lock (_syncObject) { _cancellationPending = value; } }
}
private void ReportProgress(int percentProgress)
{
this._progressReporter(new ProgressChangedEventArgs(percentProgress, null));
}
private void ProgressReporter(object arg)
{
this.OnProgressChanged((ProgressChangedEventArgs)arg);
}
protected virtual void OnProgressChanged(ProgressChangedEventArgs e)
{
if (ProgressChanged != null)
ProgressChanged(this, e);
}
public bool RunProcess(string data)
{
// This code should be in the constructor
_progressReporter = new SendOrPostCallback(this.ProgressReporter);
for (int i = 0; i < LARGE_NUMBER; ++i)
{
if (this.CancellationPending)
break;
// Do work....
// ...
// ...
// Update progress
this.ReportProgress(percentageComplete);
// Allow other threads to run
Thread.Sleep(0)
}
return true;
}
}
公共类LongRunningProcess
{
SendOrPostCallback _progressReporter;
私有只读对象_syncObject=新对象();
私有bool_cancellationPending=false;
公共事件进度变更管理者进度变更;
公共布尔取消待定
{
获取{lock(\u syncObject){return\u cancellationPending;}}
设置{lock({u syncObject){{u cancellationPending=value;}}
}
私有void报告进度(int percentProgress)
{
此._progressReporter(新ProgressChangedEventArgs(percentProgress,null));
}
私有void ProgressReporter(对象arg)
{
此.OnProgressChanged((ProgressChangedEventArgs)arg);
}
ProgressChanged(ProgressChangedEventArgs e)上受保护的虚拟无效
{
if(ProgressChanged!=null)
(本,e);
}
公共bool运行进程(字符串数据)
{
//此代码应该在构造函数中
_progressReporter=new SendOrPostCallback(this.progressReporter);
对于(int i=0;i
我喜欢将背景过程分离到一个单独的对象中。然而,我的印象是,在后台进程完成之前,您的UI线程被阻塞,因为您在同一个按钮处理程序中调用BeginInvoke和EndInvoke
MethodInvoker methodInvoker = new MethodInvoker((Action)(() => Start(lrp)));
IAsyncResult result = methodInvoker.BeginInvoke(new AsyncCallback(TransferEnded), null);
methodInvoker.EndInvoke(result);
还是我遗漏了什么?我对您使用MethodInvoker.BeginInvoke()有点困惑。您选择使用此线程而不是创建新线程并使用thread.Start()有什么原因吗 我相信您可能会阻止UI线程,因为您在与BeginInvoke相同的线程上调用EndInvoke。我想说,通常的模式是在接收线程上调用EndInvoke。对于异步I/O操作来说,这当然是正确的——如果它不适用于这里,我深表歉意。在LRP完成之前,您应该能够轻松地确定UI线程是否被阻塞 最后,您依靠BeginInvoke的一个副作用在托管线程池中的工作线程上启动LRP。同样,你应该确定这是你的意图。线程池包含排队语义,当加载大量短期进程时,它的工作非常出色。对于长时间运行的流程,我不确定它是否是一个好的选择。我倾向于使用Thread类来启动长期运行的线程 此外,虽然我认为您向LRP发送取消信号的方法会起作用,但是
MethodInvoker methodInvoker = new MethodInvoker((Action)(() => Start(lrp)));
IAsyncResult result = methodInvoker.BeginInvoke(new AsyncCallback(TransferEnded), null);
methodInvoker.EndInvoke(result);
protected virtual void OnProgressChanged(ProgressChangedEventArgs e)
{
var progressChanged = ProgressChanged;
if (progressChanged != null)
progressChanged(this, e);
}
private static object eventSyncLock = new object();
protected virtual void OnProgressChanged(ProgressChangedEventArgs e)
{
ProgressChangedEventHandler handler;
lock(eventSyncLock)
{
handler = ProgressChanged;
}
if (handler != null)
handler(this, e);
}
if( bgWorker != null && bgWorker.WorkerReportsProgress )
{
bgWorker.ReportProgress( percentage );
}