异步等待几次混淆

时间:2015-08-13 07:02:01

标签: c# asynchronous async-await task-parallel-library

正在体验新的异步&等待4.5的功能我想在进一步说明之前清除一些混淆。我一直在阅读不同的文章和关于SO的不同问题,它帮助我了解Async和Await如何工作。我将尝试将我的理解和困惑放在这里,并且如果某些代码教育我和其他正在寻找相同事物的人,我将非常感激。我用非常简单的措辞来讨论这个问题。

因此使用Async以便编译器知道由Async标记的方法包含Await操作(长操作)。最新框架包含用于异步操作的不同新内置方法。

内置异步函数(如connection.OpenAsync,ExecuteScalarAsync等)与Await关键字一起使用。我不知道这些异步方法的内部工作,但我强烈的猜测是他们正在使用任务。

我是否可以将此作为Await与任何实现Task的方法的一般规则。因此,如果我需要创建自己的执行长操作的方法,那么我会将其创建为任务吗?当它被调用时,我将使用Await Keyword吗?

第二个最重要的是,将方法创建为异步或将其创建为任务的经验法则是什么。例如,

public void SampleMain()
{
    for (int i = 1; i <= 100; i++)
    {
         DataTable dt = ReadData(int id);
    }
}

public DataTable ReadData(int id)
{ 
     DataTable resultDT = new DataTable();

     DataTable dt1 = new DataTable();
     // Do Operation to Fill DataTable from first connection string
     adapter.Fill(dt1);

     DataTable dt2 = new DataTable();
     // Do Operation to Fill DataTable from first connection string
     adapter.Fill(dt2); 

     // Code for combining datatable and returning the resulting datatable
     // Combine DataTables
     return resultDT;
}

public string GetPrimaryConnectionString()
{
     // Retrieve connection string from some file io operations
     return "some primary connection string";
}

public string GetSecondaryConnectionString()
{
     // Retrieve connection string from some file io operations
     return "some secondaryconnection string";
}

现在这是一个非常简单的场景,我根据我过去工作的一些真实世界的应用程序创建了这个场景。所以我只是想知道如何使整个过程Async。

我应该将GetPrimaryConnectionString和GetSecondaryConnectionString作为任务并在ReadData中等待它们。 ReadData也是一个任务吗?如何在SampleMain函数中调用ReadData?

另一种方法是在SampleMain中为ReadData创建一个Task并运行该Task并跳过将其他方法转换为Task。这是好方法吗?它会真的是异步吗?

3 个答案:

答案 0 :(得分:7)

  

因此使用Async以便编译器知道由Async标记的方法   包含等待操作

使用async,以便编译器可以指示从方法中创建状态机。 async方法可以没有await,但仍然有效,但它会完全同步执行。

  

内置的Async函数,如connection.OpenAsync,   ExecuteScalarAsync等与Await关键字一起使用。我不知道   这些异步方法的内部工作,但我的强烈猜测是在   他们正在使用任务的引擎盖。

Task承诺将来完成工作。有两种方法可以创建Task。但是,Task并不是唯一能代表异步操作的东西。如果您愿意,您可以自己创建一个等待它,它需要它来实现一个返回实现GetAwaiter的类型的INotifyCompletion方法。

如果您想知道如何在框架中实现方法,you can view the source。在这种特殊情况下,他们使用TaskCompletionSource<T>

  

我应该制作GetPrimaryConnectionString和   GetSecondaryConnectionString作为任务并在ReadData中等待它们。将   ReadData也是一个任务?如何在SampleMain中调用ReadData   功能

检索连接字符串没有任何固有的异步。您通常(并非总是)使用async-await自然异步IO操作。在这种特殊情况下,唯一的实际异步操作是ReadData,如果要使其异步,可以使用SqlDataReader,这会暴露异步方法。

一个例子,摘自ADO.NET teams blog

public static async Task<Product> GetProductAndReviewsAsync(
            int productID, int reviewsToGet)

{
    using (SqlConnection connection = new SqlConnection(ConnectionString))
    {
        await connection.OpenAsync();
        const string commandString = GetProductByIdCommand + ";" 
                                    + GetProductReviewsPagedById;

        using (SqlCommand command = new SqlCommand(commandString, connection))
        {
            command.Parameters.AddWithValue("productid", productID);
            command.Parameters.AddWithValue("reviewStart", 0); 
            command.Parameters.AddWithValue("reviewCount", reviewsToGet);
            using (SqlDataReader reader = await command.ExecuteReaderAsync())
            {
                if (await reader.ReadAsync())
                {
                    Product product = GetProductFromReader(reader, productID);
                    if (await reader.NextResultAsync())
                    {
                        List<Review> allReviews = new List<Review>();
                        while (await reader.ReadAsync())

                        {
                            Review review = GetReviewFromReader(reader);
                            allReviews.Add(review);
                        }
                        product.Reviews = allReviews.AsReadOnly();
                        return product;
                    }
                    else
                    {
                        throw new InvalidOperationException(
                            "Query to server failed to return list of reviews");
                    }
                }
                else
                {
                    return null;
                }
            }
        }
    }
}

答案 1 :(得分:1)

  

如何使整个过程异步

如果有adapter.Fill的异步版本,那么await中只有ReadData,而async也会// in async button click event button.Enabled = false; var dt = await ReadData(int id); button.Enabled = true; ... // do something with dt public async Task<DataTable> ReadData(int id) { ... var job1 = adapter.AsyncFill(dt1); var job2 = adapter.Fill(dt2); // wait for all of them to finish Task.WaitAll(new[] {job1, job2}); ... return Task.FromResult(resultDT); // dump approach } ,您可以在调用者中等待它方法:

Task

如果没有异步版本,那么必须创建它们(使用// in async button click event button.Enabled = false; // run synchronous task asynchronously var dt = await Task.Run(() => ReadData(int id)); button.Enabled = true; ... // do something with dt ):

async/await
对于UI,

<style name="Theme.default" parent="Theme.AppCompat.Light"> <item name="colorPrimary">@color/grey_07</item> <item name="colorPrimaryDark">@color/grey_15</item> <item name="colorAccent">@color/color_accent</item> </style> 会发光,否则(如果不涉及UI)只需创建任务并在那里运行同步操作。

答案 2 :(得分:0)

使用async-await的唯一原因是,如果你的主线程在另一个线程正在进行长度操作时可能会做一些有用的事情。如果主线程将启动另一个线程并且只等待另一个线程完成,那么最好让主线程执行操作。

主线程经常做的一件事就是保持UI响应。

你说得对,async-await使用Task,因此你会看到async函数返回一个Task。

规则:

  • 如果函数返回void,则异步版本返回Task。如果函数将返回TResult,则异步版本应返回Task <TResult&gt;。
  • 有一个例外:异步事件处理程序返回void。
  • await Task的返回值为void。等待任务<TResult&gt;的返回值是TResult。
  • 只有异步函数才能调用其他异步函数。
  • 如果您有非异步功能,您仍然可以使用异步功能。但是你不能使用等待。使用async函数的Task返回值和System.Threading.Tasks.Task方法等待结果。
  • 如果您有异步功能并希望在单独的线程中启动非异步功能,请使用:

    private int SlowCalculation(int a,int b) {     System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));     返回a + b; }

    private async Task CalculateAsync(int a,int b) {     任务myTask = Task.Run(()=&gt; SlowCalculation(a,b);     //当SlowCalcuation缓慢计算时,做其他有用的事情     //片刻之后你需要答案     int sum =等待myTask;     回报; }

看到等待任务<int&gt;的返回是int。

有些人习惯使用Task.ContinueWith等功能。由于不再需要await语句。 Await确保任务完成。等待之后的声明是您在ContinueWith中通常所做的。

在Task.Continue中您可以说:“仅在任务失败时执行此操作”。 async-await等效于try-catch。

  

请记住:如果您的线程没有任何用处(例如保持UI响应),请不要使用async-await

在async-await中启动多个任务并等待它们完成如下:

private async Task MyAsyncFunction(...)
{
    var tasks = new List<Task<int>>();
    for (int i=0; i<10; ++i)
    {
        tasks.Add(CalculateAsync(i, 2*i);
    }
    // while all ten tasks are slowly calculating do something useful
    // after a while you need the answer, await for all tasks to complete:
    await Task.WhenAll(tasks);
    // the result is in Task.Result:
    if (task[3].Result < 5) {...}
}
  

Task.Waitall的async-await版本是Task.WhenAll。 WhenAll返回一个Task而不是void,所以你可以等待它。即使在等待的时候,主线程仍然保持响应。

使用Task.WaitAll时主线程不是这样,因为你没有等待。