如何阻止直到第一个线程结束?

时间:2013-09-12 19:31:33

标签: c# .net f#

我最近遇到过这样一种情况:能够产生一堆线程,阻塞并等待一个答案(第一个到达),取消剩余的线程然后解除阻塞是很方便的。< / p>

例如,假设我有一个带有种子值的搜索功能。让我们规定搜索功能可以简单地并行化。此外,我们的搜索空间包含许多潜在的解决方案,对于某些种子值,该函数将无限期搜索,但至少有一个种子值将在合理的时间内产生解决方案。

如果我可以完全天真地进行这种搜索,那将是很好的,例如:

let seeds = [|0..100|]
Array.Parallel.map(fun seed -> Search(seed)) seeds

可悲的是,Array.Parallel.map将阻塞,直到所有线程都完成。游民。我总是可以在搜索功能中设置超时,但是我几乎肯定会等待运行时间最长的线程完成;此外,对于某些问题,超时可能不够长。

简而言之,我喜欢类似UNIX套接字select()调用的东西,仅适用于任意函数。这可能吗?如上所述,它不必处于漂亮的数据并行抽象中,也不必是F#代码。我甚至乐意使用本地库并通过P / Invoke调用它。

4 个答案:

答案 0 :(得分:4)

您可以创建一系列任务,然后使用Task.WaitAnyTask.WhenAny同步等待第一个任务完成或创建将在第一个任务完成时完成的任务。

一个简单的同步示例:

var tasks = new List<Task<int>>();
var cts = new CancellationTokenSource();

for (int i = 0; i < 10; i++)
{
    int temp = i;
    tasks.Add(Task.Run(() =>
    {
        //placeholder for real work of variable time
        Thread.Sleep(1000 * temp);

        return i;
    }, cts.Token));
}

var value = Task.WaitAny(tasks.ToArray());
cts.Cancel();

或者对于异步版本:

public static async Task<int> Foo()
{
    var tasks = new List<Task<int>>();
    var cts = new CancellationTokenSource();

    for (int i = 0; i < 10; i++)
    {
        int temp = i;
        tasks.Add(Task.Run(async () =>
        {
            await Task.Delay(1000 * temp, cts.Token);
            return temp;
        }));
    }

    var value = await await Task.WhenAny(tasks);
    cts.Cancel();

    return value;
}

答案 1 :(得分:3)

let rnd = System.Random()

let search seed = async {
    let t = rnd.Next(10000)
    //printfn "seed: %d  ms: %d" seed t
    do! Async.Sleep t
    return sprintf "seed %d finish" seed
}

let processResult result = async {
    //Todo:
    printfn "%s" result
}

let cts = new System.Threading.CancellationTokenSource()
let ignoreFun _ = () //if you don't want handle

let tasks = 
    [0..10]
    |> List.map (fun i ->
        async {
            let! result = search i
            do! processResult result
            cts.Cancel()
        }
    )

Async.StartWithContinuations(Async.Parallel tasks, ignoreFun, ignoreFun, ignoreFun, cts.Token)

答案 2 :(得分:0)

尝试使用事件对象同步所有线程,当您找到解决方案设置事件时,所有其他线程必须定期检查事件状态并停止执行(如果已设置)。

有关详细信息,请查看here

答案 3 :(得分:0)

这似乎对我有用

namespace CancellParallelLoops
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] nums = Enumerable.Range(0, 10000000).ToArray();
            CancellationTokenSource cts = new CancellationTokenSource();

            // Use ParallelOptions instance to store the CancellationToken
            ParallelOptions po = new ParallelOptions();
            po.CancellationToken = cts.Token;
            po.MaxDegreeOfParallelism = System.Environment.ProcessorCount;
            Console.WriteLine("Press any key to start. Press 'c' to cancel.");
            Console.ReadKey();

            // Run a task so that we can cancel from another thread.
            Task.Factory.StartNew(() =>
            {
                if (Console.ReadKey().KeyChar == 'c')
                    cts.Cancel();
                Console.WriteLine("press any key to exit");
            });

            try
            {
                Parallel.ForEach(nums, po, (num) =>
                {
                    double d = Math.Sqrt(num);
                    Console.WriteLine("{0} on {1}", d, Thread.CurrentThread.ManagedThreadId);
                    if (num == 1000) cts.Cancel();
                    po.CancellationToken.ThrowIfCancellationRequested();
                });
            }
            catch (OperationCanceledException e)
            {
                Console.WriteLine(e.Message);
            }

            Console.ReadKey();
        }
    }
}