Подтвердить что ты не робот

Возможно ли прервать задачу, например, прервать метод Thread (Thread.Abort)?

Мы могли бы прервать такой поток:

Thread thread = new Thread(SomeMethod);
.
.
.
thread.Abort();

Но могу ли я прервать задачу (в .Net 4.0) таким же образом, а не механизмом отмены. Я хочу немедленно удалить задачу.

4b9b3361

Ответ 1

  • Вам не следует использовать Thread.Abort()
  • Задачи могут быть отменены, но не прерваны.

Метод Thread.Abort() (строго) устарел.

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

Если вам нужно запустить процесс и убить его снаружи, единственный безопасный вариант - запустить его в отдельном AppDomain.

Ответ 2

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

Пример

У вас есть простое приложение для форм Windows, которое подключается к блокирующей синхронной веб-службе. Внутри которой он выполняет функцию на веб-службе в параллельном цикле.

CancellationTokenSource cts = new CancellationTokenSource();
ParallelOptions po = new ParallelOptions();
po.CancellationToken = cts.Token;
po.MaxDegreeOfParallelism = System.Environment.ProcessorCount;

Parallel.ForEach(iListOfItems, po, (item, loopState) =>
{

    Thread.Sleep(120000); // pretend web service call

});

Скажем в этом примере, для блокирующего вызова требуется 2 минуты. Теперь я задал свой MaxDegreeOfParallelism, чтобы сказать ProcessorCount. Для обработки iListOfItems требуется 1000 элементов.

Пользователь нажимает кнопку процесса, и цикл начинается, у нас есть "до" 20 потоков, выполняющих против 1000 элементов в коллекции iListOfItems. Каждая итерация выполняется в своем потоке. Каждый поток будет использовать переднюю нить при создании Parallel.ForEach. Это означает, что независимо от основного закрытия приложения домен приложения будет сохранен до тех пор, пока все потоки не будут завершены.

Однако пользователю необходимо закрыть приложение по какой-либо причине, скажем, закрыть форму. Эти 20 потоков будут продолжать выполняться до тех пор, пока не будут обработаны все 1000 элементов. Это не является идеальным в этом сценарии, так как приложение не выйдет, поскольку пользователь ожидает и будет продолжать работать за кулисами, что можно увидеть, взглянув на manger задачи.

Предположим, что пользователь снова пытается перестроить приложение (VS 2010), он сообщает, что exe заблокирован, тогда им придется перейти в диспетчер задач, чтобы убить его или просто ждать, пока все 1000 элементов будут обработаны.

Я бы не стал обвинять вас в том, что вы сказали, но, конечно же! Я должен отменить эти потоки, используя CancellationTokenSource и вызывая Отмена... но есть некоторые проблемы с этим как .net 4.0. Во-первых, это все равно никогда не приведет к прерыванию потока, который будет предлагать исключение прерывания, за которым следует прекращение потока, поэтому домену приложения вместо этого придется ждать завершения потоков, как правило, и это означает ожидание последнего блокирующего вызова, которая была бы самой последней итерацией (нитью), которая в конечном итоге вызывает вызов po.CancellationToken.ThrowIfCancellationRequested. В примере это означает, что домен приложения может оставаться в живых до 2 минут, хотя форма закрыта и отменена.

Обратите внимание, что Calling Cancel on CancellationTokenSource не генерирует исключение в потоке (-ях) обработки, который действительно действовал бы, чтобы прервать блокирующий вызов, подобный прерыванию потока, и остановить выполнение. Исключение готово к кешированию, когда все остальные потоки (параллельные итерации) заканчиваются и возвращаются, исключение генерируется в инициирующем потоке (где объявлен цикл).

Я выбрал не, чтобы использовать параметр CancellationTokenSource.IsCancellationRequested, которая в противном случае была бы другой опцией.

Что-то вроде следующего, если условие было бы подходящим в цикле;

if (loopState.ShouldExitCurrentIteration || loopState.IsExceptional || stopExecuting) {loopState.Stop(); возвращение;}

Итерация теперь выйдет "контролируемым" способом, а также прекратит дальнейшие итерации, но, как я уже сказал, это мало для нашей проблемы, когда приходится ждать долгого и блокирующего вызова (-ов), которые сделаны внутри каждая итерация (поток параллельной петли), так как они должны быть выполнены до того, как каждый поток сможет перейти к опции проверки, должен ли он остановиться.

Таким образом, по мере того, как пользователь закрывает форму, 20 потоков будут сигнализироваться для остановки через stopExecuting, но они остановятся только тогда, когда они закончат выполнение своего долгого вызова функции.

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

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

CancellationTokenSource cts = new CancellationTokenSource();
ParallelOptions po = new ParallelOptions();
po.CancellationToken = cts.Token;
po.MaxDegreeOfParallelism = System.Environment.ProcessorCount;

Parallel.ForEach(iListOfItems, po, (item, loopState) =>
{

    using (cts.Token.Register(Thread.CurrentThread.Abort))
    {
        Try
        {
           Thread.Sleep(120000); // pretend web service call          
        }
        Catch(ThreadAbortException ex)
        {
           // log etc.
        }
        Finally
        {
          // clean up here
        }
    }

});

но это все равно приведет к исключению в объявляющем потоке.

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

Ответ 3

Но могу ли я прервать задачу (в .Net 4.0) таким же образом не отмена механизм. Я хочу немедленно удалить задачу.

Другие ответчики сказали вам не делать этого. Но да, вы можете это сделать. Вы можете предоставить Thread.Abort() в качестве делегата, который будет вызван механизмом отмены задачи. Вот как вы можете настроить это:

class HardAborter
{
  public bool WasAborted { get; private set; }
  private CancellationTokenSource Canceller { get; set; }
  private Task<object> Worker { get; set; }

  public void Start(Func<object> DoFunc)
  {
    WasAborted = false;

    // start a task with a means to do a hard abort (unsafe!)
    Canceller = new CancellationTokenSource();

    Worker = Task.Factory.StartNew(() => 
      {
        try
        {
          // specify this thread Abort() as the cancel delegate
          using (Canceller.Token.Register(Thread.CurrentThread.Abort))
          {
            return DoFunc();
          }
        }
        catch (ThreadAbortException)
        {
          WasAborted = true;
          return false;
        }
      }, Canceller.Token);
  }

  public void Abort()
  {
    Canceller.Cancel();
  }

}

отказ от ответственности: не делайте этого.

Вот пример того, что не делать:

 var doNotDoThis = new HardAborter();

 // start a thread writing to the console
 doNotDoThis.Start(() =>
    {
       while (true)
       {
          Thread.Sleep(100);
          Console.Write(".");
       }
       return null;
    });


 // wait a second to see some output and show the WasAborted value as false
 Thread.Sleep(1000);
 Console.WriteLine("WasAborted: " + doNotDoThis.WasAborted);

 // wait another second, abort, and print the time
 Thread.Sleep(1000);
 doNotDoThis.Abort();
 Console.WriteLine("Abort triggered at " + DateTime.Now);

 // wait until the abort finishes and print the time
 while (!doNotDoThis.WasAborted) { Thread.CurrentThread.Join(0); }
 Console.WriteLine("WasAborted: " + doNotDoThis.WasAborted + " at " + DateTime.Now);

 Console.ReadKey();

output from sample code

Ответ 4

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

На практике, если вы прервите поток, вы должны сделать это только вместе с убийством процесса. К сожалению, слишком многие люди думают, что ThreadAbort - это жизнеспособный способ остановить что-то и продолжить, это не так.

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

Ответ 5

Все знают (надеюсь), что это плохо, чтобы прекратить поток. Проблема в том, что у вас нет части кода, который вы вызываете. Если этот код выполняется в некотором бесконечном цикле do/while, который сам вызывает некоторые нативные функции и т.д., Вы в основном застряли.

Итак, что бы это ни стоило, я написал те две блокирующие функции, которые используют свой собственный собственный поток, а не поток из пула или какой-то поток, созданный CLR. Они остановят поток, если истечет время ожидания:

// returns true if the call went to completion successfully, false otherwise
public static bool RunWithAbort(this Action action, int milliseconds) => RunWithAbort(action, new TimeSpan(0, 0, 0, 0, milliseconds));
public static bool RunWithAbort(this Action action, TimeSpan delay)
{
    if (action == null)
        throw new ArgumentNullException(nameof(action));

    var source = new CancellationTokenSource(delay);
    var success = false;
    var handle = IntPtr.Zero;
    var fn = new Action(() =>
    {
        using (source.Token.Register(() => TerminateThread(handle, 0)))
        {
            action();
            success = true;
        }
    });

    handle = CreateThread(IntPtr.Zero, IntPtr.Zero, fn, IntPtr.Zero, 0, out var id);
    WaitForSingleObject(handle, 100 + (int)delay.TotalMilliseconds);
    CloseHandle(handle);
    return success;
}

// returns what the function should return if the call went to completion successfully, default(T) otherwise
public static T RunWithAbort<T>(this Func<T> func, int milliseconds) => RunWithAbort(func, new TimeSpan(0, 0, 0, 0, milliseconds));
public static T RunWithAbort<T>(this Func<T> func, TimeSpan delay)
{
    if (func == null)
        throw new ArgumentNullException(nameof(func));

    var source = new CancellationTokenSource(delay);
    var item = default(T);
    var handle = IntPtr.Zero;
    var fn = new Action(() =>
    {
        using (source.Token.Register(() => TerminateThread(handle, 0)))
        {
            item = func();
        }
    });

    handle = CreateThread(IntPtr.Zero, IntPtr.Zero, fn, IntPtr.Zero, 0, out var id);
    WaitForSingleObject(handle, 100 + (int)delay.TotalMilliseconds);
    CloseHandle(handle);
    return item;
}

[DllImport("kernel32")]
private static extern bool TerminateThread(IntPtr hThread, int dwExitCode);

[DllImport("kernel32")]
private static extern IntPtr CreateThread(IntPtr lpThreadAttributes, IntPtr dwStackSize, Delegate lpStartAddress, IntPtr lpParameter, int dwCreationFlags, out int lpThreadId);

[DllImport("kernel32")]
private static extern bool CloseHandle(IntPtr hObject);

[DllImport("kernel32")]
private static extern int WaitForSingleObject(IntPtr hHandle, int dwMilliseconds);

Ответ 6

using System;
using System.Threading;
using System.Threading.Tasks;

...

var cts = new CancellationTokenSource();
var task = Task.Run(() => { while (true) { } });
Parallel.Invoke(() =>
{
    task.Wait(cts.Token);
}, () =>
{
    Thread.Sleep(1000);
    cts.Cancel();
});

Это простой фрагмент, чтобы прервать бесконечную задачу с CancellationTokenSource.

Ответ 7

Если у вас есть конструктор Task, то мы можем извлечь Thread из Task и вызвать thread.abort.

Thread th = null;

Task.Factory.StartNew(() =>
{
    th = Thread.CurrentThread;

    while (true)
    {
        Console.WriteLine(DateTime.UtcNow);
    }
});

Thread.Sleep(2000);
th.Abort();
Console.ReadKey();