Многопоточность и согласованность с C#

В окне формы Windows несколько событий могут запускать асинхронный метод. Этот метод загружает файл и кэширует его. Моя проблема в том, что я хочу, чтобы этот метод был выполнен один раз. Другими словами, я хочу предотвратить многократную загрузку файла.

Если метод загрузки файла запускается дважды, я хочу, чтобы второй вызов дождался файла (или дождался выполнения первого метода).

Есть ли у кого-нибудь идеи, как этого добиться?

ОБНОВИТЬ: Я просто пытаюсь предотвратить ненужные загрузки. В моем случае, когда клиент наводит указатель мыши на элемент в ListBox более чем на пару миллисекунд, мы начинаем загрузку. Мы предполагаем, что пользователь щелкнет и запросит файл. Что потенциально может случиться, так это то, что пользователь удерживает указатель мыши на элементе в течение одной секунды, а затем щелкает. В этом случае начинаются две загрузки. Я ищу лучший способ справиться с таким сценарием.

ОБНОВЛЕНИЕ 2:: есть вероятность, что пользователь наведет указатель мыши на несколько элементов. В результате произойдет несколько загрузок. Я не очень серьезно отношусь к этому сценарию, но прямо сейчас, если мы столкнемся с таким сценарием, мы не отказываемся от загрузки. Файл будет загружен (файлы обычно составляют около 50-100 КБ), а затем будет кэширован.

Если вы можете исправить некоторые из моих предположений (см. Ниже), я могу обновить свой ответ с помощью кода.

Michael Haren 15.01.2009 00:33

Более подробная информация поможет. Вы пытаетесь гарантировать, что пропускная способность не распределяется между двумя конкурирующими загрузками, или избегаете ненужных загрузок одного и того же ресурса?

AnthonyWJones 15.01.2009 00:33

Можете ли вы скачать сразу несколько файлов? Если они передумают до завершения загрузки, откажетесь ли вы от первой загрузки и начнете новую?

Michael Haren 15.01.2009 01:39

Если вы действительно можете загружать только один файл за раз, я бы отключил обработчик асинхронных событий во время его работы и повторно подключил его, когда он закончился.

Michael Haren 15.01.2009 01:40
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
4
1 336
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Вы можете просто заключить вызов метода в оператор блокировки, подобный этому

private static readonly Object padLock = new Object();

...
lock(padLock)
{
    YourMethod();
}

В вопросе указано, что метод является асинхронным, IOW он быстро возвращается после отправки запроса на загрузку. Следовательно, этот ответ ничего не дает.

AnthonyWJones 15.01.2009 00:29

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

Matthew Brubaker 15.01.2009 00:32

Фактически, второй асинхронный вызов, который понимает, что первый все еще обрабатывается, останется асинхронным с точки зрения вызывающей стороны. Для этого потребуется, чтобы все проверки выполнялись внутри самого асинхронного метода.

Harper Shelby 15.01.2009 00:35

Возможно, я слишком поспешил с ответом и предположил, что кое-что мне не следует делать ... Я подожду более подробной информации (в надежде не потерять за это время другие голоса :))

Stefano Driussi 15.01.2009 00:37

Я не уверен, как это будет сделано в C#, но в java вы должны синхронизировать частный статический конечный объект в классе перед загрузкой файла. Это заблокирует любые дальнейшие запросы, пока не будет завершен текущий. Затем вы можете проверить, был ли загружен файл или нет, и действовать соответствующим образом.

private static final Object lock = new Object();
private File theFile;

public method() {
  synchronized(lock) {
    if (theFile != null) {
      //download the file
    }
  }
}

synchronize {} в Java примерно эквивалентен lock {} в C#

Spencer Ruport 15.01.2009 00:30

Я был почти уверен, что есть эквивалент, просто не знал, что это такое.

Matthew Brubaker 15.01.2009 00:31
Ответ принят как подходящий

Сохраняйте состояние того, что происходит в переменной формы, и пусть ваш асинхронный метод проверяет это состояние, прежде чем он что-либо сделает. Однако убедитесь, что вы синхронизируете доступ к нему! Для этого подходят мьютексы и семафоры.

Если вы можете загружать разные файлы одновременно, вам нужно будет отслеживать, что загружается, в списке для справки.

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

Вообще говоря, вам следует по возможности использовать в .Net оператор lock {}. Семафоры и мьютексы слишком надежны для такого рода ситуаций.

Spencer Ruport 15.01.2009 00:27

Я бы согласился, если бы проблема была определена более четко. Я много гадал.

Michael Haren 15.01.2009 00:29

Есть идеи, почему это было отклонено? Мне это кажется вполне разумным ответом.

Matthew Brubaker 15.01.2009 00:30

Итак, если я хорошо понимаю, вы говорите мне сделать что-то подобное: if (this.IsUpdating == false) {lock (padLock) {if (this.IsUpdating == false) {...}}}. Подойдет ли здесь шаблон двойной проверки?

Martin 15.01.2009 01:22

Вы должны положить весь чек в замок. Хотя, конечно, нужно уточнить, чего вы хотите.

Michael Haren 15.01.2009 01:34

Я добавил несколько комментариев к вашему вопросу - мне нужно еще немного

Michael Haren 15.01.2009 01:40

Я просто добавил новые детали. Еще раз спасибо!

Martin 15.01.2009 01:59

Вот фиктивная реализация, которая поддерживает загрузку нескольких файлов:

    Dictionary<string, object> downloadLocks = new Dictionary<string, object>();

    void DownloadFile(string localFile, string url)
    {
        object fileLock; 
        lock (downloadLocks)
        {
            if (!downloadLocks.TryGetValue(url, out fileLock))
            {
                fileLock = new object(); 
                downloadLocks[url] = fileLock;
            }
        }

        lock (fileLock)
        {
            // check if file is already downloaded 

            // if not then download file
        }
    }

В общем, я согласен с Майкл, используйте lock вокруг кода, который фактически получает файл. Однако, если сначала происходит одно событие, и вы всегда можете загрузить файл, рассмотрите возможность использования Фьючерсы. В исходном случае запускайте будущий бег

Future<String> file = InThe.Future<String>(delegate { return LoadFile(); });

и в любом другом случае ждите будущего значения

DoSomethingWith(file.Value);

Если вы хотите, чтобы один поток ждал, пока другой поток завершит задачу, вы, вероятно, захотите использовать ManualResetEvent. Может быть, примерно так:

private ManualResetEvent downloadCompleted = new ManualResetEvent();
private bool downloadStarted = false;

public void Download()
{
   bool doTheDownload = false;

   lock(downloadCompleted)
   {
       if (!downloadStarted)
       { 
            downloadCompleted.Reset();
            downloadStarted = true;
            doTheDownload = true;
       }
   }

   if (doTheDownload)
   {
       // Code to do the download

       lock(downloadCompleted)
       {
           downloadStarted = false;
       }
       // When finished notify anyone waiting.
       downloadCompleted.Set();
   }
   else
   {
       // Wait until it is done... 
       downloadCompleted.WaitOne();
   }

}

обратите внимание, что это будет поддерживать только одну загрузку за раз, если вы хотите 2 одновременных загрузки разных файлов и только 2 потока, возможно, потоки и очереди с синхронизацией являются лучшим выбором

Sam Saffron 15.01.2009 01:30

Я согласен ... но на самом деле я просто пытался проиллюстрировать использование ResetEvents, а не реализовывать за него весь проект. :)

bobwienholt 15.01.2009 22:56

Другие вопросы по теме