6

使用 ASP.NET 4.5,我正在尝试使用新的 async/await 玩具。我有一个 IDataReader 实现类,它包装了特定于供应商的阅读器(如 SqlDatareader)。我有一个简单的 ExecuteSql() 方法,它可以像这样同步操作:

public IDataReader ReaderForSql(string sql)
{
    var cmd = NewCommand(sql, CommandType.Text);
    return DBReader.ReaderFactory(cmd.ExecuteReader());
}

我想要的是这个的异步版本。这是我的第一次尝试:

public Task<IDataReader> ReaderForSqlAsync(string sql, CancellationToken ct)
{
    var cmd = NewCommand(sql, CommandType.Text);
    return cmd.ExecuteReaderAsync(ct)
              .ContinueWith(t => DBReader.ReaderFactory(t.Result));
}

我用它:

using (var r = await connection.ReaderForSqlAsync("SELECT ...", cancellationToken))
{
    ...
}

到目前为止,这在我有限的测试中效果很好。但是在看了这个 Cloud9 视频几次之后:http : //channel9.msdn.com/Events/aspConf/aspConf/Async-in-ASP-NET 我对他们给出的警告感到担心:

  • ContinueWith 消耗额外的线程池资源 - Readerfactory 很轻!
  • 任务.结果阻塞

并且由于我将 ContinuationToken 传递给 ExecuteReaderAsync() 似乎取消只是 ExecuteReaderAsync() 可能失败的另一个原因(毕竟是 SQL!)

当我尝试 ContinueWith 时,任务的状态是什么?t.Result 会阻塞吗?扔?做错事了吗?

4

2 回答 2

5

ContinueWith默认情况下将使用当前任务调度程序(线程池线程),但您可以通过传递TaskContinuationOptions.ExecuteSynchronously和显式更改它TaskScheduler

也就是说,我会首先努力:

public async Task<IDataReader> ReaderForSqlAsync(string sql, CancellationToken ct)
{
  var cmd = NewCommand(sql, CommandType.Text);
  var readerResult = await cmd.ExecuteReaderAsync(ct).ConfigureAwait(false);
  return DBReader.ReaderFactory(readerResult);
}

async并以一致的方式为您await处理所有ContinueWith美味佳肴和边缘条件。如果性能测试表明这是一个严重的问题,则可能会使此代码复杂化以更快。

于 2012-11-30T20:06:46.393 回答
2

Result如果任务未完成,则阻塞。但是在延续处理程序中它已经有了。所以它不会阻塞。你在做正确的事。

当您调用Result错误任务时(并且您说这可能会发生),将重新抛出异常。这会导致您的继续出现相同的异常,从而导致返回的最终任务ReaderForSqlAsync也出现错误。这是一件好事:整个任务链出现故障并且所有异常都已观察到(与被吞没相反)。所以这也是最佳实践。

使用线程进行计算密集型工作总是可以的。再说一次,您使用 ContinueWith 做的事情是正确的。毕竟你必须计算IDataReader 某个地方。你不能不计算它。

于 2012-11-30T19:39:17.720 回答