C#:如果一个线程失败,则终止其他线程

时间:2016-06-24 11:48:09

标签: c# multithreading asp.net-web-api task

如果其中一个线程失败,有没有办法停止执行其他并发线程?我正在开发一个ASP.NET MVC网站,它将大量使用API​​调用。我不是以顺序方式发送太多的API请求,而是创建线程,每个线程都消耗一个API调用。如果任何线程失败,我想杀死其他线程。我怎样才能做到这一点?我使用Task.WaitAll但它允许所有其他线程执行,即使一个线程失败。请帮忙。

更新: 为了模拟实时场景,我创建了一个控制台应用程序。请参考以下代码。我想让第五个线程失败。第五个线程失败后,我希望所有其他正在运行的线程停止。

public class Program
{
        static int[] delays = { 3, 2, 10, 4, 5, 6, 7, 8, 9, 1 };
        static int[] ids = new int[10];
        static Task[] tasks = null;
        static CancellationTokenSource tokenSource = new CancellationTokenSource();
        static IList<int> threadsExecuted = new List<int>();

        static void Main(string[] args)
        {
            ids = Enumerable.Range(1, 10).ToArray();

            try
            {
                tasks = ids.Select(id => MyTaks(id)).ToArray();
                Task.WaitAll(tasks);
            }
            catch (Exception exception)
            {
                Console.WriteLine("Exception in Main::\nMessage: " + exception.Message +
                    "StackTrace: " + exception.StackTrace +
                    "InnerException: " + exception.InnerException);
            }

            Console.WriteLine("\n\nThreads executed: " + string.Join(", ", threadsExecuted.OrderBy(id => id).ToArray()));

            Console.WriteLine("\n\n\nExit..");

            Console.Read();
        }

        private static async Task MyTaks(int id)
        {
            var delay = delays[id - 1] * 1000;

            Console.WriteLine("Thread id #" + id + " started with delay " + delay + " seconds.");

            CancellationToken cToken = tokenSource.Token;

            Task task = new Task(() =>
            {
                Thread.Sleep(delay);

                if (id == 5) //Fail
                {
                    Console.WriteLine("Cancelling..");

                    throw new Exception("Thread id #" + id + " failed.");
                }
            }, cToken);

            task.Start();

            await task;

            Console.WriteLine("Thread id #" + id + " executed.");

            threadsExecuted.Add(id);
        }

}

3 个答案:

答案 0 :(得分:2)

  

我不是以顺序方式发送太多API请求,而是创建线程,每个线程都使用一次API调用。

这种方法几乎会破坏服务器的任何可扩展性。您应该使用异步并发(多个操作)而不是并行并发(多个线程)。您的API代码将使用HttpClient.GetAsync并传递CancellationToken

所以,你的测试代码看起来像这样:

static void Main(string[] args)
{
  ids = Enumerable.Range(1, 10).ToArray();

  try
  {
    tasks = ids.Select(id => MyTaks(id)).ToArray();
    Task.WaitAll(tasks);
  }
  catch (Exception exception)
  {
    Console.WriteLine("Exception in Main::\nMessage: " + exception.Message +
                "StackTrace: " + exception.StackTrace +
                "InnerException: " + exception.InnerException);
  }

  lock (threadsExecuted)
    Console.WriteLine("\n\nThreads executed: " + string.Join(", ", threadsExecuted.OrderBy(id => id).ToArray()));

  Console.WriteLine("\n\n\nExit..");

  Console.Read();
}

private static async Task MyTaks(int id)
{
  var delay = delays[id - 1] * 1000;

  Console.WriteLine("Task id #" + id + " started with delay " + delay + " seconds.");

  CancellationToken cToken = tokenSource.Token;

  // Production code would use `await httpClient.GetAsync(url, token)` here.
  await Task.Delay(delay, cToken);

  if (id == 5) //Fail
  {
    Console.WriteLine("Cancelling..");
    tokenSource.Cancel();
    throw new Exception("Thread id #" + id + " failed.");
  }

  Console.WriteLine("Thread id #" + id + " executed.");
  lock (threadsExecuted)
    threadsExecuted.Add(id);
}

答案 1 :(得分:1)

如果要取消任务集合

,可以尝试这样的事情
 // Define the cancellation token.
  CancellationTokenSource source = new CancellationTokenSource();
  CancellationToken token = source.Token;

  Random rnd = new Random();
  Object lockObj = new Object();

  List<Task<int[]>> tasks = new List<Task<int[]>>();
  TaskFactory factory = new TaskFactory(token);
  for (int taskCtr = 0; taskCtr <= 10; taskCtr++) {
     int iteration = taskCtr + 1;
     tasks.Add(factory.StartNew( () => {
                                   int value;
                                   int[] values = new int[10];
                                   for (int ctr = 1; ctr <= 10; ctr++) {
                                      token.ThrowIfCancellationRequested();
                                      lock (lockObj) {
                                         value = rnd.Next(0,101);
                                      }
                                      if (value == 0) { 
                                         source.Cancel();
                                         Console.WriteLine("Cancelling at task {0}", iteration);
                                         break;
                                      }   
                                      values[ctr-1] = value; 
                                   }
                                   return values;
                                }, token));   

  }
  try {
     Task<double> fTask = factory.ContinueWhenAll(tasks.ToArray(), 
                                                  (results) => {
                                                     Console.WriteLine("Calculating overall mean...");
                                                     long sum = 0;
                                                     int n = 0; 
                                                     foreach (var t in results) {
                                                        foreach (var r in t.Result) {
                                                              sum += r;
                                                              n++;
                                                           }
                                                     }
                                                     return sum/(double) n;
                                                  } , token);
     Console.WriteLine("The mean is {0}.", fTask.Result);
  }   
  catch (AggregateException ae) {
     foreach (Exception e in ae.InnerExceptions) {
        if (e is TaskCanceledException)
           Console.WriteLine("Unable to compute mean: {0}", 
                             ((TaskCanceledException) e).Message);
        else
           Console.WriteLine("Exception: " + e.GetType().Name);
     }
  }
  finally {
     source.Dispose();
  }

更新:使用取消令牌取消网络API 您需要在API中接受取消令牌

[AsyncTimeout(150)]
[HandleError(ExceptionType = typeof(TimeoutException),
                                    View = "TimeoutError")]
public async Task<ActionResult> GizmosCancelAsync(
                       CancellationToken cancellationToken )
{
    ViewBag.SyncOrAsync = "Asynchronous";
    var gizmoService = new GizmoService();
    return View("Gizmos",
        await gizmoService.GetGizmosAsync(cancellationToken));
}

返回Task<ActionResult>的异步操作方法是可取消的,即当CancellationToken提供一个参数时,它们会带有AsyncTimeout attribute参数。

msdn reference

答案 2 :(得分:0)

您需要使用Task.WaitAll()CancellationToken版本。

更多信息 - Cancellation in Managed Threads