При реализации ограниченных по времени методов я должен прервать рабочий поток или позволить ему работать? - PullRequest
4 голосов
/ 21 декабря 2010

В настоящее время я пишу интерфейс на основе веб-служб в существующее приложение.Для этого я использую WCF LOB Adapter SDK , который позволяет создавать пользовательские привязки WCF, которые предоставляют внешние данные и операции в виде веб-служб.

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

Под руководством расследованиямне на вопрос " Реализация общего тайм-аута C # ", который мудро советует использовать рабочий поток.Вооружившись этим знанием, я могу написать:

public MetadataRetrievalNode[] Browse(string nodeId, int childStartIndex,
    int maxChildNodes, TimeSpan timeout)
{
    Func<MetadataRetrievalNode[]> work = () => {
        // Return computed metadata...
    };

    IAsyncResult result = work.BeginInvoke(null, null);
    if (result.AsyncWaitHandle.WaitOne(timeout)) {
        return work.EndInvoke(result);
    } else {
        throw new TimeoutException();
    }
}

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

public MetadataRetrievalNode[] Browse(string nodeId, int childStartIndex,
    int maxChildNodes, TimeSpan timeout)
{
    Thread workerThread = null;
    Func<MetadataRetrievalNode[]> work = () => {
        workerThread = Thread.CurrentThread;
        // Return computed metadata...
    };

    IAsyncResult result = work.BeginInvoke(null, null);
    if (result.AsyncWaitHandle.WaitOne(timeout)) {
        return work.EndInvoke(result);
    } else {
        workerThread.Abort();
        throw new TimeoutException();
    }
}

Теперь прерывание потока обычно считается неправильным .Он прерывает текущую работу, теряет ресурсы, портит блокировку и даже не гарантирует, что поток действительно прекратит работу.Тем не менее, HttpResponse.Redirect() прерывает поток каждый раз, когда он вызывается, и IIS, кажется, вполне доволен этим.Может быть, он готов как-то с этим справиться.Мое внешнее приложение, вероятно, не является.

С другой стороны, если я позволю рабочему потоку запустить свой курс, кроме увеличения конкуренции за ресурсы (меньше доступных потоков в пуле), не будет утечка памятив любом случае, потому что work.EndInvoke() никогда не вызывается?Точнее говоря, не будет ли массив MetadataRetrievalNode[], возвращаемый work, навсегда остаться?

Это только вопрос выбора меньшего из двух зол, или есть способ не прервать рабочий потоки до сих пор вернуть память, используемую BeginInvoke()?

Ответы [ 2 ]

6 голосов
/ 22 декабря 2010

Ну, во-первых, Thread.Abort не так плохо, как раньше.В 2.0 было сделано несколько улучшений в CLR, которые исправили несколько основных проблем с прерыванием потоков.Имейте в виду, что это все еще плохо, поэтому избегать этого - лучший образ действий.Если вам нужно прибегнуть к прерыванию потоков, то, по крайней мере, вы должны рассмотреть возможность удаления домена приложения, откуда произошел прерывание.Это будет невероятно инвазивным в большинстве сценариев и не разрешит возможное повреждение неуправляемых ресурсов.

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

Лучший способ действий - провести ваш Func<MetadataRetrievalNode[]> делегатский опрос aпеременная в безопасных точках, чтобы увидеть, должна ли она прекратить выполнение самостоятельно.

public MetadataRetrievalNode[] Browse(string nodeId, int childStartIndex, int maxChildNodes, TimeSpan timeout)
{
    bool terminate = false;

    Func<MetadataRetrievalNode[]> work = 
      () => 
      {
        // Do some work.

        Thread.MemoryBarrier(); // Ensure a fresh read of the terminate variable.
        if (terminate) throw new InvalidOperationException();

        // Do some work.

        Thread.MemoryBarrier(); // Ensure a fresh read of the terminate variable.
        if (terminate) throw new InvalidOperationException();

        // Return computed metadata...
      };

    IAsyncResult result = work.BeginInvoke(null, null);
    terminate = !result.AsyncWaitHandle.WaitOne(timeout);
    return work.EndInvoke(result); // This blocks until the delegate completes.
}

Хитрая часть заключается в том, как бороться с блокировкой вызовов внутри вашего делегата.Очевидно, вы не можете проверить флаг terminate, если делегат находится в середине блокирующего вызова.Но, предполагая, что блокирующий вызов инициируется одним из стандартных механизмов ожидания BCL (WaitHandle.WaitOne, Monitor.Wait и т. Д.), Вы можете использовать Thread.Interrupt, чтобы «нажать» на него, и это должно немедленно разблокировать его.

1 голос
/ 22 декабря 2010

Ответ зависит от типа работы, выполняемой вашим рабочим потоком. Я предполагаю, что он работает с внешними ресурсами, такими как подключение к данным. Thread.Abort () действительно является злом в любом случае потоков, работающих с перехватами к неуправляемым ресурсам, независимо от того, насколько хорошо они упакованы.

По сути, вы хотите, чтобы ваше обслуживание прекратило работу, если оно истекло. На этом этапе, теоретически, вызывающей стороне больше не важно, сколько времени займет поток; его волнует только то, что он «слишком длинный» и должен двигаться дальше. Запрет на ошибку в рабочем методе рабочего потока, он в конечном итоге закончится; звонящему просто наплевать, потому что он больше не ждет.

Теперь, если причина, по которой истекло время ожидания потока, заключается в том, что он захвачен в бесконечном цикле или ему приказано бесконечно ждать какой-либо другой операции, такой как вызов службы, тогда у вас есть проблема, которую вы должны исправить, но это исправление не убивать нить. Это было бы аналогично отправке вашего ребенка в продуктовый магазин за хлебом, пока вы ждете в машине. Если ваш ребенок продолжает тратить 15 минут в магазине, когда вы думаете, что это должно занять 5, вам в конечном итоге становится любопытно, зайдите и узнайте, что они делают. Если это не то, что, как вы думали, они должны делать, как если бы они проводили все время, глядя на кастрюли и сковородки, вы «исправляете» их поведение в будущем. Если вы зайдете и увидите, что ваш ребенок стоит в длинной очереди, тогда вы просто начинаете ждать дольше. Ни в одном из этих случаев вы не должны нажимать кнопку, которая взрывает жилет, который они носят; это просто создает большую неразбериху, которая, скорее всего, помешает способности следующего ребенка выполнить то же самое дело позже.

...