0

我有一个应用程序,我在其中将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();
            }
        }
    }
}
4

1 回答 1

1

您在锁内调用“获取属性”,但您也没有在锁内调用 AppendBlock API。这引入了一个竞争条件,其中可能有以下操作顺序:

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

一种解决方案是将 AppendBlock 调用也放入锁中。

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

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

于 2016-02-05T18:11:11.803 回答