синхронизировать потоки - нет интерфейса - PullRequest
6 голосов
/ 17 марта 2010

Я пытаюсь написать многопоточный код и сталкиваюсь с некоторыми вопросами синхронизации. Я знаю, что здесь много постов, но я не смог найти ничего подходящего.

У меня есть System.Timers.Timer, который проходит каждые 30 секунд, он идет в БД и проверяет, есть ли новые задания. Если он находит его, он выполняет задание в текущем потоке (таймер открывает новый поток для каждого прошедшего). Во время выполнения задания мне нужно уведомить основной поток (где находится таймер) о ходе выполнения.

Примечания:

  1. У меня нет пользовательского интерфейса, поэтому я не могу сделать beginInvoke (или использовать фоновый поток), как это обычно делается в winforms.
  2. Я подумал реализовать ISynchronizeInvoke в своем основном классе, но это выглядит немного излишне (возможно, я ошибаюсь).
  3. У меня есть событие в моем классе заданий и регистр основного класса, и я вызываю событие всякий раз, когда мне нужно, но я беспокоюсь, что это может привести к блокировке.
  4. Каждая работа может занять до 20 минут.
  5. Одновременно может быть запущено до 20 заданий.

Мой вопрос:

Как правильно уведомить мою основную ветку о любом продвижении в моей ветке работы?

Спасибо за любую помощь.

Ответы [ 4 ]

2 голосов
/ 17 марта 2010

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

class JobManager
{
    private object synchObject = new object();

    private int _ActiveJobCount;

    public int ActiveJobsCount
    {
        get { lock (this.synchObject) { return _ActiveJobCount; } }
        set { lock (this.synchObject) { _ActiveJobCount = value; } }
    }

    public void Start(Action job)
    {
        var timer = new System.Timers.Timer(1000);

        timer.Elapsed += (sender, e) =>
        {
            this.ActiveJobsCount++;
            job();
            this.ActiveJobsCount--;
        };

        timer.Start();
    }
}

Пример:

class Program
{
    public static void Main(string[] args)
    {
        var manager = new JobManager();

        manager.Start(() => Thread.Sleep(3500));

        while (true)
        {
            Console.WriteLine(manager.ActiveJobsCount);

            Thread.Sleep(250);
        }
    }
}
1 голос
/ 17 марта 2010

Вы можете уведомить основной поток о прогрессе с помощью метода обратного вызова. То есть:

// in the main thread
public void ProgressCallback(int jobNumber, int status)
{
    // handle notification
}

Вы можете передать этот метод обратного вызова в рабочий поток, когда вызываете его (то есть как делегат), или код рабочего потока может «неявно» знать о нем. В любом случае работает.

Параметры jobNumber и status являются лишь примерами. Возможно, вы захотите использовать другой способ определения выполняемых заданий, и вы можете использовать перечисляемый тип для статуса. Как бы вы это ни делали, имейте в виду, что ProgressCallback будет вызываться несколькими потоками одновременно, поэтому, если вы обновляете какие-либо общие структуры данных или записываете данные журнала, вам придется защищать эти ресурсы с помощью блокировок или других методов синхронизации.

Вы также можете использовать события для этого, но поддержание актуальности подписок на события основного потока может быть потенциальной проблемой. У вас также есть вероятность утечки памяти, если вы забудете отписать основной поток от событий определенного рабочего потока. Хотя события, безусловно, будут работать, я бы порекомендовал обратный вызов для этого приложения.

1 голос
/ 17 марта 2010

Если вы не возражаете против зависимости от .NET 3.0, вы можете использовать Диспетчер для распределения запросов между потоками. Он ведет себя аналогично Control.Invoke () в Windows Forms, но не имеет зависимости Forms. Вам нужно будет добавить ссылку на сборку WindowsBase (часть .NET 3.0 и новее и является основой для WPF)

Если вы не можете зависеть от .NET 3.0, то я бы сказал, что вы с самого начала нашли правильное решение: внедрите интерфейс ISynchronizeInvoke в своем основном классе и передайте это свойство SynchronizingObject таймера. Затем ваш обратный вызов таймера будет вызываться в главном потоке, который затем может порождать BackgroundWorkers , который проверяет БД и запускает любые поставленные в очередь задания. Задания будут сообщать о ходе выполнения события ProgressChanged , которое автоматически направит вызов в основной поток.

Быстрый поиск в Google показал этот пример о том, как на самом деле реализовать интерфейс ISynchronizeInvoke.

1 голос
/ 17 марта 2010

Используйте события. Например, класс BackgroundWorker разработан специально для ваших целей.

http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

Функция ReportProgress вместе с событием ProgressChanged - это то, что вы будете использовать для обновления прогресса.

pullJobTimer.Elapsed += (sender,e) =>
{
    BackgroundWorker worker = new BackgroundWorker();
    worker.WorkerReportsProgress = true;
    worker.DoWork += (s,e) =>
    {
        // Whatever tasks you want to do
        // worker.ReportProgress(percentComplete);
    };
    worker.ProgressChanged += mainThread.ProgressChangedEventHandler;
    worker.RunWorkerAsync();
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...