当log csv为appendblob时,C#lock无法正常工作

时间:2016-02-05 09:07:18

标签: c#-4.0 async-await azure-storage-blobs linqtocsv

我有一个应用程序,我在其中登录 azure appendblob 作为csv。我使用LinqToCsv-> CsvContext写为csv。

每次我写日志,我检查appendblob是否有任何长度,如果它是零,那么我将标题写入csv。 在我以多线程方式进行测试之前,每件事情都工作正常。因此,不会在csv中多次写入标题。

我使用 lock 语句阻止多个线程访问语句块,但它没有按预期工作。

请让我知道我在这里做错了什么。

这是我的代码:

public async Task WriteToAudit(AuditData auditData)
{
    _auditBlobName = Utilities.GetAuditAppendBlobName(auditData.Container);

    var appendBlob = await GetAppendBlobReferenceAsync();

    var list = new List<AuditData> { auditData };

    var auditDataBytes = CloudAppendBlobHelper.WriteCsvWithHeaderToMemory(list, appendBlob);

    using (var stream = new MemoryStream(auditDataBytes))
    {
        await appendBlob.AppendBlockAsync(stream).ConfigureAwait(false);
    }
}

public static class CloudAppendBlobHelper
{
    private static readonly object syncLock = new object();

    public static byte[] WriteCsvWithHeaderToMemory(IEnumerable<AuditData> records, CloudAppendBlob appendBlob)
    {
        lock (syncLock)
        {
            appendBlob.FetchAttributes();

            var outputFileDescription = new CsvFileDescription
            {
                SeparatorChar = ',',
                EnforceCsvColumnAttribute = true,
                FirstLineHasColumnNames = appendBlob.Properties.Length <= 0
            };

            using (var memoryStream = new MemoryStream())
            {
                using (var streamWriter = new StreamWriter(memoryStream))
                {
                    var context = new CsvContext();
                    context.Write(records, streamWriter, outputFileDescription);
                }
                return memoryStream.ToArray();
            }
        }
    }
}

1 个答案:

答案 0 :(得分:1)

您正在呼叫&#39;获取属性&#39;在一个锁内,但你也不会在锁中调用AppendBlock API。这引入了竞争条件,其中可以执行以下操作顺序:

  • 线程1进入WriteCsvWithHeaderToMemory,然后退出。包含标题,因为blob的长度为0.
  • 线程2进入WriteCsvWithHeaderToMemory,然后退出。包含标题,因为blob的长度是(仍然)0。
  • 线程1调用AppendBlock并写入标题。
  • 线程2调用AppendBlock并写入标题。

一种解决方案是将AppendBlock调用放在锁内。

如果要保持并行性,另一种解决方案是使用AccessCondition。在编写标题时,可以使用&#34; ifAppendPositionEqual&#34;条件,或者&#34; ifMaxSizeLessThanOrEqual&#34;条件,例如。如果不满足条件,这些将导致写操作在服务上失败。然后,您需要捕获失败,并重做没有标题的追加操作。如果你这样做,我认为你应该能够完全移除锁。

请注意,您在AppendBlock调用时需要知道您是否正在编写标题。