Изменение переменной класса из цикла foreach TPL - PullRequest
1 голос
/ 06 марта 2020

У меня есть следующее консольное приложение, которое удаляет большие двоичные объекты из контейнера учетной записи хранения старше 7 дней.

Я пытаюсь создать отзыв l oop на экране консоли, вызвав метод WriteNumberFilesProcessed ( ), который увеличивает переменную-член _counter после удаления большого двоичного объекта и затем записывает в консоль.

Проблема заключается в том, что последняя задача завершается в TPL foreach l oop, а не в переменной-члене _counter, идущей из С 29999 по 30000, это будет что-то еще, например, 29995.

Я не уверен, что мне нужно сделать, чтобы убедиться, что TPL foreach l oop получает актуальный член varaible ?

class Program
{
    static int _counter = 0;
    static int _numBlobsToDelete = 30000;
    static int _blobCount = 0;

    static void Main(string[] args)
    {
        GetOldBlobs();

        Console.ReadLine();
    }

    static async void GetOldBlobs()
    {
        try
        {
            Stopwatch stopWatch = new Stopwatch();

            CloudStorageAccount acc = CloudStorageAccount.Parse("");
            var client = acc.CreateCloudBlobClient();
            var container = client.GetContainerReference("");
            var rollingTotal = 0;

            while (true)
            {
                stopWatch.Restart();
                Console.WriteLine($"{DateTime.Now}:\tGetting blobs older than a week in a batch of {_numBlobsToDelete}...");

                var blobs = container.ListBlobs("", true).OfType<CloudBlockBlob>().Where(b => (DateTime.UtcNow.AddDays(-7) > b.Properties.LastModified.Value.DateTime)).Take(_numBlobsToDelete).ToList();
                _blobCount = blobs.Count();
                _counter = 0;

                Console.WriteLine($"{DateTime.Now}:\tTime taken to get {_numBlobsToDelete} blobs - {stopWatch.Elapsed}");
                Console.WriteLine($"{DateTime.Now}:\tDeleting {_blobCount} blobs...");

                try
                {
                    Parallel.ForEach(blobs, blob => DeleteBlob(blob));
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"{DateTime.Now}:\t{ex.Message}");
                }

                rollingTotal += _blobCount;

                Console.WriteLine($"\n{DateTime.Now}:\tTime taken to delete blobs - {stopWatch.Elapsed}");
                Console.WriteLine($"{DateTime.Now}:\t{rollingTotal} blobs deleted since startup");
                Console.WriteLine($"{DateTime.Now}:\tSleeping for 5 seconds...\n");
                await Task.Delay(5000);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"{DateTime.Now}:\t{ex.Message}");
        }
    }

    public static void DeleteBlob(CloudBlockBlob blob)
    {
        try
        {
            blob.Delete();
            WriteNumberFilesProcessed();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"{DateTime.Now}:\t{ex.Message}\n");
        }
    }

    public static void WriteNumberFilesProcessed()
    {
        _counter++;
        Console.Write($"\r{_counter} of {_blobCount}");
    }
}

1 Ответ

5 голосов
/ 06 марта 2020

Ваша проблема в том, что несколько потоков вызывают _counter++ одновременно. Это приводит к состоянию гонки.

Чтобы исправить это, проще всего использовать один из Interlocked методов, например, так:

Interlocked.Increment(ref _counter);

Без использования блокировки, может произойти следующее: (данный поток A и поток B):

Предположим, _counter имеет значение 1:

Thread A: reads value of _counter -> 1
Thread B: reads value of _counter -> 1
Thread A: adds 1 to the value it read -> 2
Thread B: adds 1 to the value it read -> 2
Thread A: writes value back to _counter -> 2
Thread B: writes value back to _counter -> 2

И теперь _counter имеет значение 2 вместо значения, которое должно иметь, 3.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...