使用TPL / async的递归异步调用等待

时间:2015-06-11 18:39:56

标签: c# .net multithreading asynchronous recursion

我正在寻找使用C#异步功能(TPL / async / await)以递归方式处理分层结构。以下是我正在尝试做的事情的概述

我有一个要处理的作业集合,如下所示。每个工作都有事可做,也可以有一个或多个孩子也有事可做。所有的父和子作业都调用相同的功能来完成实际的工作"这个函数在"异步" (以下代码)

public void Process()
{
    WebJob[] jobs = CreateWebJobs(); // dummy jobs

    // first level 
    Parallel.ForEach(jobs,
                new ParallelOptions { MaxDegreeOfParallelism = 2 }, // parallelism hardcoded for simplicity
                (job) => ExecuteJob(job));
}

private void ExecuteJob(WebJob job, [CallerMemberName] string memberName = "")
{
    Console.ForegroundColor = ConsoleColor.DarkYellow;
    Console.WriteLine("Caller> {0} :: {1} Job> {2} :: {3} Thread> {4}", memberName, "\t", job.Name, "\t", Thread.CurrentThread.ManagedThreadId);

    Task t = GetDataAsync(job);
    t.Wait(); // needed such that parent response is received before children start over (?).


    if (job.Children != null)
    {
        job.Children.ToList().ForEach((r) =>
        {
            r.ParentResponse = job.Response; // Children need parent's response
            ExecuteJob(r);
        });
    }
}

private async Task GetDataAsync(WebJob j)
{
    // This is just test code. Ideally it would be an external call to some "async" method
    await Task.Delay(1000);
    j.Response = string.Format("{0} complete", j.Name);
    Console.ForegroundColor = ConsoleColor.Cyan;
    Console.WriteLine("parentResp>> {0} :: {1} Job>> {2} :: {3} Thread>> {4}", j.ParentResponse, "\t", j.Name, "\t", Thread.CurrentThread.ManagedThreadId);
    Console.WriteLine("--------------");
}

private WebJob[] CreateWebJobs()
{
    return new WebJob[] {
        new WebJob() { Id=1, Name = "Job1", ExecURL = "http://url1", 
            Children = new WebJob[] 
            {
                new WebJob() 
                { 
                    Id=2, Name = "Job2", ExecURL = "http://url2", 
                    Children = new WebJob[] 
                    {
                        new WebJob() { Id=4, Name = "Job4", ExecURL = "http://url4" }
                    }
                },
                new WebJob() 
                { 
                    Id=3, Name = "Job3", ExecURL = "http://url3" 
                }
            }
        },
        new WebJob() { Id=5, Name = "Job5", ExecURL = "http://url5"}                
    };
}
  1. 层次结构中有3个级别。

  2. 我想要开始并行处理第一级(Job1,Job2,Job3)。

  3. 一旦他们并行开始,每个单独的工作将开始处理 本身,等待其处理完成(重要),然后继续递归处理其子项,直到层次结束。子项依赖于父项处理的数据,因此它们等待父处理完成。

  4. 处理实际"工作" (由父节点和子节点调用)异步发生,因为调用方法异步工作 - 因此一个"新线程"不是必需的(Task.StartNew())。

  5. 以下是我用来演示场景的示例代码 -

        <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/tools"
        xmlns:card_view="http://schemas.android.com/apk/res/com.rahavardnovin.Sada"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <android.support.v7.widget.CardView
            android:id="@+id/card_view"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            card_view:cardCornerRadius="@dimen/radius_medium"
            card_view:cardElevation="@dimen/cardview_default_elevation"
            card_view:cardUseCompatPadding="true">
    
            <!-- date  -->
            <com.rahavardnovin.Sada.ui.customui.PersianTextView
                android:id="@+id/newsDate"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="@dimen/global_padding"
                android:layout_marginTop="3dp"
                android:textSize="@dimen/textsize_smallest" />
    
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="right"
                android:layout_marginBottom="@dimen/global_padding"
                android:layout_marginLeft="@dimen/global_padding"
                android:layout_marginRight="@dimen/global_padding"
                android:layout_marginTop="@dimen/actionbar_items_size"
                android:gravity="right"
                android:orientation="horizontal">
    
                <com.makeramen.RoundedImageView
                    android:id="@+id/newsImage"
                    android:layout_width="@dimen/album_photo_width"
                    android:layout_height="@dimen/album_photo_height"
                    android:src="@drawable/avatar_agent"
                    app:riv_corner_radius="4dp" />
    
                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="right"
                    android:layout_marginBottom="@dimen/global_padding_small"
                    android:orientation="vertical"
                    android:paddingRight="10dp">
    
    
                    <com.rahavardnovin.Sada.ui.customui.PersianTextView
                        android:id="@+id/newsTitle"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="right"
                        android:ellipsize="end"
                        android:textColor="@color/black_dark"
                        android:textSize="@dimen/textsize_large" />
    
                    <com.rahavardnovin.Sada.ui.customui.PersianTextView
                        android:id="@+id/newsContent"
                        style="@style/Textview.Gray40"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_gravity="right"
                        android:ellipsize="end"
                        android:maxLines="4"
                        android:textSize="@dimen/abc_text_size_button_material" />
                </LinearLayout>
    
            </LinearLayout>
    
        </android.support.v7.widget.CardView>
    </RelativeLayout>
    
    • 处理方法从启动所有&#34;第一级&#34;开始。工作 平行。
    • ExecuteJob方法接管并递归转到 孩子们完成所有处理

    这没关系,但我不相信这种递归异步模式是否是一种有效的方法。我想避免t.Wait()。我已经尝试过,在我的理解中看起来没有什么不同,我也读到了ForEachAsync模式,并想知道这是否合适。此解决方案最终将成为ASP.NET Web API服务。关于这种递归异步模式的任何想法?

2 个答案:

答案 0 :(得分:4)

如果GetDataAsync是唯一的阻止操作,那么您可以在整个过程中使用异步编程,从而无需Parallel.ForEach次调用或阻止Wait次调用。

public async Task Process()
{
    WebJob[] jobs = CreateWebJobs(); // dummy jobs

    await Task.WhenAll(jobs.Select(ExecuteJob));
}

private async Task ExecuteJob(WebJob job, [CallerMemberName] string memberName = "")
{
    Console.ForegroundColor = ConsoleColor.DarkYellow;
    Console.WriteLine("Caller> {0} :: {1} Job> {2} :: {3} Thread> {4}", memberName, "\t", job.Name, "\t", Thread.CurrentThread.ManagedThreadId);

    await GetDataAsync(job);

    if (job.Children != null)
    {
        var childTasks = job.Children.Select(r =>
        {
            r.ParentResponse = job.Response;
            return ExecuteJob(r);
        });

        await Task.WhenAll(childTasks);
    }
}

修改:如果顶级方法应该阻止(而不是冒着让消费者忘掉的风险),请执行以下操作:

public void Process()
{
    WebJob[] jobs = CreateWebJobs(); // dummy jobs

    Task.WaitAll(jobs.Select(ExecuteJob));
}

答案 1 :(得分:2)

由于您的核心是异步的,因此您根本不应该使用并行或多线程。你想要的是没有 parallelism 并发 - 即异步并发,通常用public async Task ProcessAsync() { WebJob[] jobs = CreateWebJobs(); await Task.WhenAll(jobs.Select(x => ExecuteJobAsync(x))); } private async Task ExecuteJobAsync(WebJob job, [CallerMemberName] string memberName = "") { Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine("Caller> {0} :: {1} Job> {2} :: {3} Thread> {4}", memberName, "\t", job.Name, "\t", Thread.CurrentThread.ManagedThreadId); await GetDataAsync(job); if (job.Children != null) { var childTasks = job.Children.Select(async x => { x.ParentResponse = job.Response; // Children need parent's response await ExecuteJobAsync(x); }); await Task.WhenAll(childTasks); } } 完成。

这是双重的,因为您计划部署到ASP.NET,其中并行性可能会显着降低您的可伸缩性。

public static class MyFragment extends PreferenceFragment {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        addPreferencesFromResource(R.xml.pref_general);

        PreferenceScreen root = this.getPreferenceScreen();

    }
}