Изящно закрывающее многопоточное приложение?

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

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

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

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

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

РЕДАКТИРОВАТЬ: После мощных ответов я изменил код

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 ответа

Решение

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

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

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

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

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

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

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

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");
    }
}

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

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