Изящно закрывающееся многопоточное приложение? - PullRequest
5 голосов
/ 28 июня 2009

У меня есть приложение с двумя потоками.

Первый (основной поток), который захватывает данные с использованием сокета и обновляет DataTables

Второй Вставляет DataTables в базу данных.

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

В настоящее время я использую следующее решение для преодоления «прерывания во время вставки»

EDIT: После мощных ответов я сменил код

void MainThread()
{
     while(Read())
     {
        //Read Data through socket
        try
        {
           //Wait on Mutex1
           //Update Tables
        }
        finally
        {
          //Release Mutex1
        }
     }
   _isrunning = false;
   _secondThread.Join();
}
void SecondThread()
{
     while(_isrunning)
     {
        try
        {
           //Wait on Mutex1
           //Insert Tables into Database using transactions
        }
        finally
        {
           //Release Mutex1           
        }
     }
}

Ответы [ 3 ]

8 голосов
/ 28 июня 2009

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

Я показал подходящую очередь производителя / потребителя здесь - работник просто будет:

void WriterLoop() {
    SomeWorkItem item; // could be a `DataTable` or similar
    while(queue.TryDequeue(out item)) {
        // process item
    }
    // queue is empty and has been closed; all done, so exit...
}

Вот полный пример, основанный на SizeQueue<> - обратите внимание, что процесс не завершится, пока читатель и writer не завершат работу корректно. Если вы не хотите опустошать очередь (т.е. вы хотите быстрее выйти из системы и забыть о незавершенной работе), тогда хорошо - добавьте куда-нибудь дополнительный (изменчивый) флаг.

static class Program {
    static void Write(object message) {
        Console.WriteLine(Thread.CurrentThread.Name + ": " + message);
    }
    static void Main() {
        Thread.CurrentThread.Name = "Reader";
        Thread writer = new Thread(WriterLoop);
        writer.Name = "Writer";
        var queue = new SizeQueue<int>(100);
        writer.Start(queue);
        // reader loop - note this can run parallel
        // to the writer
        for (int i = 0; i < 100; i++) {
            if (i % 10 == 9) Write(i);
            queue.Enqueue(i);
            Thread.Sleep(5); // pretend it takes time
        }
        queue.Close();
        Write("exiting");
    }
    static void WriterLoop(object state) {
        var queue = (SizeQueue<int>)state;
        int i;
        while (queue.TryDequeue(out i)) {
            if(i%10==9) Write(i);
            Thread.Sleep(10); // pretend it takes time
        }
        Write("exiting");
    }
}
6 голосов
/ 28 июня 2009

Предполагать, что "метод прерывания вызова" означает прерывание потока с помощью Thread.Abort Не делай этого .

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

Тем не менее, вы не должны получать противоречивые данные в вашей БД при сбое приложения, поэтому у вас есть транзакции БД, которые имеют свойства ACID .

ОЧЕНЬ ВАЖНОЕ РЕДАКТИРОВАНИЕ Вы сказали: вы не используете транзакции по соображениям производительности, а вместо этого используете мьютексы. Это НЕПРАВИЛЬНО на нескольких уровнях. Во-первых, транзакции могут ускорить выполнение определенных операций, например, попробуйте вставить 10 строк в таблицу, попробуйте еще раз в транзакции, версия транзакции будет быстрее. Во-вторых, что происходит, когда / если ваше приложение дает сбой, вы повредите свою БД? Что происходит, когда запущено несколько экземпляров вашего приложения? Или когда вы запускаете отчеты для своей БД в анализаторе запросов?

3 голосов
/ 28 июня 2009

Ваше ожидание мьютекса должно включать тайм-аут. Внешний цикл каждого потока может проверять флаг «пожалуйста, закройте сейчас». Чтобы завершить работу, установите флажок «пожалуйста, закройте сейчас» для каждого потока, а затем используйте «присоединиться», чтобы дождаться завершения каждого потока.

...