如何防止这种竞争条件?

How to prevent this race condition?

我们有一个 Client class 它扩展了 BaseClass.

BaseClass 有这个方法:

    protected void Proxy()
    {
        Error = null;
        _proxy = new WebServiceClient<T>(_fullURL);
        Error = _proxy.Error;
    }

    protected virtual void Cleanup()
    {
        if (_proxy != null)
        {
            _proxy.Dispose();
            _proxy = null;
        }
    }

并且 Client 包含多个并行调用的操作。 客户端不是单例,我们每次生成一个实例。

操作如下:

public void OperationAsync(Action<BaseResult> callback)
{

    TaskCompletionSource<String> taskSrc = new TaskCompletionSource<String>();
    Task<String> tsk = taskSrc.Task;
    try
    {
        Proxy();

        ThreadPool.QueueUserWorkItem(t =>
        {
            try
            {
                String result = _proxy.Channel.ExecuteOperation(SecurityToken());
                taskSrc.SetResult(result);
            }
            catch (Exception ex)
            {
                taskSrc.SetException(ex);
            }
        });

        tsk.Wait();

        BaseResult r = new BaseResult();
        r.Value = tsk.Result;
        r.Error = tsk.Exception;
        Cleanup();

        if (callback != null)
        {
            callback(r);
        }

    }
    catch (Exception ex)
    {
        FileManager.Log(ex);
    }
}

如您所见,每个操作都会调用 ProxyCleanUp 操作。

我们尚未发现任何行为模式,但有时(可能一天一次)我们会在日志文件中看到此错误:

出现一个或多个错误.. InnerException: System.ObjectDisposedException: 无法访问已释放的对象。 对象名称:'System.ServiceModel.Channels.ServiceChannel'.

它不会发生在任何特定的操作上。它一直在变化。 我相信 Proxy 需要在构造函数期间完成,而 CleanUp 需要在处置期间完成,但这意味着要改变几件事,我想确定一下。

如果有任何关于如何改进它的想法,我将不胜感激。

由于您的原始代码正在调用 tsk.Wait(); 您在后台线程上 运行 您的代理代码时阻塞了调用线程。这样做没有任何好处,而且可能会增加开销。

因此,以下是防止竞争条件的方法:

public void OperationAsync(Action<BaseResult> callback)
{
    try
    {
        var r = new BaseResult();
        using (var proxy = new WebServiceClient<T>(_fullURL))
        {
            try
            {
                r.Value = proxy.Channel.ExecuteOperation(SecurityToken());
            }
            catch (Exception ex)
            {
                r.Error = ex;
            }
        }
        if (callback != null)
        {
            callback(r);
        }
    }
    catch (Exception ex)
    {
        FileManager.Log(ex);
    }
}