C#线程和Windows窗体

C#线程和Windows窗体,c#,multithreading,concurrency,C#,Multithreading,Concurrency,我使用后台进程实现响应GUI的方法正确吗?如果没有,请批评并提出改进。特别是,指出哪些代码可能会遇到死锁或争用情况 工作线程需要能够被取消并报告其进度。我没有使用BackgroundWorker,因为我看到的所有示例都在表单本身上有流程代码,而不是单独的对象。我曾考虑为BackgroundWorker继承LongRunningProcess,但我认为这会在对象上引入不必要的方法。理想情况下,我不希望有一个对流程的表单引用(“lrp”),但我不知道如何取消流程,除非lrp上有一个事件检查调用方的标

我使用后台进程实现响应GUI的方法正确吗?如果没有,请批评并提出改进。特别是,指出哪些代码可能会遇到死锁或争用情况

工作线程需要能够被取消并报告其进度。我没有使用BackgroundWorker,因为我看到的所有示例都在表单本身上有流程代码,而不是单独的对象。我曾考虑为BackgroundWorker继承LongRunningProcess,但我认为这会在对象上引入不必要的方法。理想情况下,我不希望有一个对流程的表单引用(“lrp”),但我不知道如何取消流程,除非lrp上有一个事件检查调用方的标志,但这似乎不必要地复杂,甚至可能是错误的

Windows窗体(编辑:已将*.EndInvoke调用移动到回调)

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 );
    }