将ThreadStatic属性用于静态变量时,实例为null

时间:2017-05-25 11:30:33

标签: c# asp.net-mvc multithreading thread-safety

我有使用System.Threading.Task调用服务功能的Action方法。

服务函数有一个静态全局变量,我已设置属性ThreadStatic以使我的函数成为线程安全。

我的问题是,有时当我运行我的操作方法时,共享变量_sharedList会在我的HotelService中访问它时抛出空引用异常。

以下是复制问题的示例实现:

调用HotelService.TestMultiThread的控制器操作方法

public ActionResult MultiThread()
{
  HotelService svc = new HotelService();
  var resp = new List<TestPnrHeaderResponse>();

  var tasks = Enumerable.Range(0, 5).Select(i => Task.Run(() => svc.TestMultiThread(i)));

  var results = await Task.WhenAll(tasks);

  return View(resp);
}

酒店服务类

  

_sharedList在行_sharedList.listInt.AddRange(GetIntList());

中为空
public class HotelService
{
    [ThreadStatic]
    private static TestPnrHeaderResponse _sharedList;

    private void LoadCache()
    {
        _sharedList = new TestPnrHeaderResponse();
        _sharedList.PnrLegs = new List<PnrLegVM>();
        _sharedList.listInt = new List<int>();
        Task.Factory.StartNew(() =>
        {
            _sharedList.listInt.AddRange(GetIntList());
        });
    }

    private IEnumerable<int> GetIntList()
    {
        return Enumerable.Range(0, 5);
    }
    public TestPnrHeaderResponse TestMultiThread(int count)
    {
        LoadCache();

        if (count % 2 == 0)
        {
            _sharedList.PnrLegs.Add(new PnrLegVM
            {
                ApplicationType = count.ToString(),
                PKCity = count,
                PKNationality = 1,
                PKPnrHeader = 1,
                PKPnrLeg = 1
            });
        }
        else
        {
            _sharedList.PnrLegs.Add(new PnrLegVM
            {
                ApplicationType = count.ToString(),
                PKCity = count,
                PKNationality = 99,
                PKPnrHeader = 99,
                PKPnrLeg = 99
            });

        }
        return _sharedList;
    }
}

共享变量类

public class TestPnrHeaderResponse
{
   public List<PnrLegVM> PnrLegs { get; set; }
   public List<int> listInt { get; set; }
}
public class PnrLegVM
{
   public int PKPnrLeg{get;set;}

   public int PKPnrHeader{get;set;}      
   public string ApplicationType{get;set;}
   public int PKNationality {get;set;}
}

请帮助找到解决方案。此外,是否有更好的方法使函数Thread-safe,因为它在实际实现中使用了许多共享变量。

2 个答案:

答案 0 :(得分:0)

  

_sharedList在行_sharedList.listInt.AddRange(GetIntList())中为空;

令人惊讶的是,此时总是 null。您正在一个全新的线程中执行该代码,其中称为LoadCache(),因此尚未初始化该字段。如果您不小心得到非null值,那只是因为您在线程池线程中执行,其中 count参数的其他值,调用了LoadCache()方法。当然,在这种情况下,虽然值不是null,但它不是您认为的列表。

我不清楚为什么要将该工作委托给另一个线程,特别是当您尝试将_sharedList变量绑定到一个线程时。我也不清楚为什么你在没有任何同步的情况下修改列表,即使你显然希望该对象是两个不同的,不同步的线程中使用的对象。

但是暂时忽略这些问题,可以通过捕获局部变量中的值并在匿名方法中使用它来解决null问题:

private void LoadCache()
{
    _sharedList = new TestPnrHeaderResponse();
    _sharedList.PnrLegs = new List<PnrLegVM>();
    _sharedList.listInt = new List<int>();

    List<int> listInt = _sharedList.listInt;

    Task.Factory.StartNew(() =>
    {
        listInt.AddRange(GetIntList());
    });
}

本地listInt变量将获取对_sharedList对象引用的列表的引用,然后在匿名方法中捕获该变量,以确保它是您真正想要的列表用于该任务。

在您发布的代码中,TestMultiThread()方法实际上并未使用_sharedList.listInt,因此理论上没有冲突。但是在你的问题中没有足够的上下文来知道该字段是否在其他时间用于其他地方。坦率地说,甚至不清楚为什么_sharedList首先是static字段。您发布的所有代码都可以通过使变量成为局部变量来编写,仅使用显示的方法。

但是,如果你真的需要代码按照你所示的方式工作,上面的内容将解决你所询问的null值。

答案 1 :(得分:0)

根据上述建议和here,我已使用ConcurrentDictionary<TKey, Lazy<TValue>>确定以下实施方式:

<强>控制器

public ActionResult MultiThread()
{
 HotelService svc = new HotelService();
 var resp = new List<TestPnrHeaderResponse>();
 var tasks = Enumerable.Range(0, 5).Select(i => Task.Run(() => svc.TestMultiThread(i)));
 var results = await Task.WhenAll(tasks);
 return View(resp);
}

酒店服务类

public class HotelService
{
    private ConcurrentDictionary<int, Lazy<TestPnrHeaderResponse>> _sharedList
        = new ConcurrentDictionary<int, Lazy<TestPnrHeaderResponse>>();

    private TestPnrHeaderResponse LoadGetCache(int count)
    {
        var resp = new Lazy<TestPnrHeaderResponse>();
        resp.Value.PnrLegs = new List<PnrLegVM>();
        resp.Value.listInt = new List<int>();
        List<int> listInt = resp.Value.listInt;
        Task.Factory.StartNew(() =>
        {
            listInt.AddRange(GetIntList());
        });

        return resp.Value;
    }

    private IEnumerable<int> GetIntList()
    {
        return Enumerable.Range(0, 5);
    }
    public TestPnrHeaderResponse TestMultiThread(int count)
    {
        var resp = new TestPnrHeaderResponse();
        resp = _sharedList.GetOrAddLazy(count,(k)=> LoadGetCache(k));

        if (resp == null)
        {
            LoadGetCache(count);
        }
        if (resp != null)
        {
            if (count % 2 == 0)
            {
                resp.PnrLegs.Add(new PnrLegVM
                {
                    ApplicationType = count.ToString(),
                    PKCity = count,
                    PKNationality = 1,
                    PKPnrHeader = 1,
                    PKPnrLeg = 1
                });
                resp.StatusCode = System.Net.HttpStatusCode.Accepted;
            }
            else
            {
                resp.PnrLegs.Add(new PnrLegVM
                {
                    ApplicationType = count.ToString(),
                    PKCity = count,
                    PKNationality = 99,
                    PKPnrHeader = 99,
                    PKPnrLeg = 99
                });
                resp.StatusCode = System.Net.HttpStatusCode.Redirect;
            }
        }

        return resp;
    }
}

回答here

的扩展方法
public static V GetOrAddLazy<T, V>(this System.Collections.Concurrent.ConcurrentDictionary<T, Lazy<V>> dictionary, T key, Func<T, V> valueFactory)
{
      var lazy = dictionary.GetOrAdd(key, new Lazy<V>(() => valueFactory(key), LazyThreadSafetyMode.ExecutionAndPublication));
      return lazy.Value;
}

希望它可以帮助某人面对同样的问题。感谢。