System.ArgumentException: 目标数组的长度不够。请检查 destIndex 和长度以及数组的下限

扫码支付接口将要上线,近几天在优化系统性能。昨天把日志Helper类的日志记录改成了使用Queue<T>对象来实现异步处理。
做了单元测试,并模拟多线程来测试后,发现正常。
今天将站点部署到准生产环境,用loadrunner压测时,发现运行一段时间后报如下异常,并且导致iis进程挂掉:

2017/2/16 11:50:08    [ClientPayAPI_115007438_1CD2C]酷宝获取二维码异常:System.ArgumentException: 目标数组的长度不够。请检查 destIndex 和长度以及数组的下限。
   在 System.Array.Copy(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 length, Boolean reliable)
   在 System.Collections.Generic.Queue`1.SetCapacity(Int32 capacity)
   在 System.Collections.Generic.Queue`1.Enqueue(T item)
   在 CommonUtils.LogHelper.InputFile(String log, LogType logType)
   在 CommonUtils.LogHelper.Write(String logText)
   在 PaymentBLL.HttpMessageSignService.ValidPayRequestSign_New(String requestJson)
   在 PaymentPlatform.Kubao.QuickPay.ClientPayAPI.ExecFun_New(String request)
   在 PaymentPlatform.Kubao.QuickPay.ClientPayAPI.ProcessRequest(HttpContext context)

本地再次模拟高并发测试,发现复现的几率很小。不过,无论如何,既然好在可以复现,就要解决。

经查,Queue<T>以及List<T>这些泛型类不是线程安全的。在并发操作时,内部操作可能会出现问题。

通过ILSpy(.Net 反编译软件,可以打开.NET 的exe和DLL等程序集,前提要求程序集未加密/未加壳/未做强度混淆),或者去微软官方(referencesource.microsoft.com,.net 已经开源了 )可以查看Enqueue方法的实现,可知并未控制并发。

#region 程序集 mscorlib.dll, v4.0.0.0
// C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.0mscorlib.dll
#endregion

// Adds item to the tail of the queue.
//
/// <include file='docQueue.uex' path='docs/doc[@for="Queue.Enqueue"]/*' />
public void Enqueue(T item) {
    if (_size == _array.Length) {
        int newcapacity = (int)((long)_array.Length * (long)_GrowFactor / 100);
        if (newcapacity < _array.Length + _MinimumGrow) {
            newcapacity = _array.Length + _MinimumGrow;
        }
        SetCapacity(newcapacity);
    }

    _array[_tail] = item;
    _tail = (_tail + 1) % _array.Length;
    _size++;
    _version++;
}

对此问题,解决方案有2个:

【方案一】入队时使用并发锁lock。

【方案二】使用ConcurrentQueue<T>。ConcurrentQueue<T>表示线程安全的先进先出 (FIFO) 集合。这个类在.net类库的System.Collections.Concurrent下。System.Collections.Concurrent命名空间提供多个线程安全集合类。当有多个线程并发访问集合时,应使用这些类来代替System.Collections和System.Collections.Generic命名空间中的对应类型。

原文地址:https://www.cnblogs.com/buguge/p/6410407.html