【问题标题】:What is the correct way to run synchronous method in asynchronously异步运行同步方法的正确方法是什么
【发布时间】:2017-01-25 00:32:51
【问题描述】:

我有一个抽象类,它的方法是这样定义的

public abstract class FooBase : IDisposable
{
    protected abstract bool GetData(params object[] getDataParams);
}

我正在使用这样的抽象类并尝试异步运行 GetData 方法

public class SomeClass : FooBase
{
    protected override bool GetData(params object[] getDataParams)
    {
        var task = Task.Factory.StartNew(() =>
        {
            using (var adapter = new DataAdapter())
            {
                // Take your time and fetch the data
            }
        });
        task.Wait();
        return true;
    }
}

我想弄清楚的是:我这样做是对还是错,或者是否有任何其他更好的方法可以实现相同的目标。

更新

如果我像这样改变我的方法,它会被异步调用还是不被异步调用

public class SomeClass : FooBase
{
    protected override bool GetData(params object[] getDataParams)
    {
        var task = Task.Factory.StartNew(async () =>
        {
            using (var adapter = new DataAdapter())
            {
                // Take your time and fetch the data
            }
        });
        task.Wait();
        return true;
    }
}

感谢您的 cmets。

【问题讨论】:

  • 您的使用不是异步的——您是在后台线程中启动任务,但主线程只是坐在那里等待它。直接返回任务或await任务或类似。根据定义,抽象类中的方法是同步的,所以除非你能改变你的运气。
  • 查看TaskCompletionSource
  • 请发布您的实际数据访问代码,而不是包装器。 ADO.NET 具有异步方法,例如 ExecuteReaderAsyncExecuteNonQueryAsync。像 EF 这样的 ORM 和像 Dapper 这样的 microORM 也提供异步方法。很可能您的实际数据访问代码可以修改为异步运行。
  • 回复:您的更新 - 您的主线程仍在阻塞此方法,并且它总是直到您的方法签名更改。你能解释一下你想在这里实现什么吗?您只是想避免阻塞 UI 线程,还是实际上期望操作系统级别的异步?
  • 好的,Richard 的回答指出你不能通过包装方法来实现操作系统级别的异步——这不会改变幕后发生的事情。您需要为此使用异步 API。关于不阻塞 UI 线程,为了实现这一点,您需要更改 GetData 的方法签名,因此它也返回一个任务(通过直接返回包装的任务或使用 async/await) - 你需要一直到你的 UI 执行此操作,否则 UI 线程仍将被阻塞在某个地方。

标签: c# asynchronous task-parallel-library .net-4.5


【解决方案1】:

除了同步之外,没有其他方法可以运行同步方法。

您可以将结果包装在看起来像是异步运行的东西中(例如Task.FromResult),或者在另一个线程中运行。1但是同步方法仍然会阻塞它所在的线程继续运行。

(相反,阻塞异步操作是微不足道的。这就是为什么你需要让底层操作异步的原因,因为你可以在上面构建同步和异步方法。)

更新(针对有问题的更新):

附加代码——特别是task.Wait() 语句——将导致调用者的线程在等待任务完成时阻塞。该任务将在另一个线程上运行,导致该线程阻塞。 IE。您正在导致两个线程(调用者和一个线程池线程)阻塞。如果直接调用底层方法,则只会阻塞调用者的线程。

你有两种方法:

  1. 最佳:使用 ADO.NET 的异步操作。 (这意味着不使用DataTables/DataAdaptor,但恕我直言,无论如何这是一个很好的举措:他们所做的只是将应该在数据库上完成的操作移动到客户端。)

  2. 卸载到另一个线程,但将Task<TResult>返回给调用者,仅将Task标记为完成,然后底层操作完成。比如:

    protected override Task<bool> GetData(params object[] getDataParams) {
      var tcs = new TaskCompletionSource<bool>();
      Task.Factory.StartNew(async () => {
        using (var adapter = new DataAdapter()) {
          // Take your time and fetch the data
          tcs.SetResult(the-result);
        }
      });
      return tcs.Task;
    }
    

    注意这里GetData 的返回是Task&lt;bool&gt;:调用者需要等待或同时执行其他操作,然后获取数据操作的结果。如果调用者只是等待,那么你有两个阻塞的线程。 (C# 5 的await 不同:调用者也变成异步的。)


1 为免生疑问:这可以看起来像一个正常的、Task&lt;T&gt; 返回的异步方法(例如,利用 TaskCompletionSource&lt;T&gt; 从当前加载阻塞线程到线程池中的某个线程)。但这仍然是一个阻塞操作:只是阻塞了一个不同的线程。

【讨论】:

  • 我认为这里的术语有点模糊 - 我会说包装后台线程的任务可以被认为是异步的(至少从消费者的角度来看)。
  • @AntP 不完全——用任务包装阻塞调用就是所谓的假异步。异步(不等待完成)与并发(并行执行许多事情)不同。 Task.Run 将启动运行同步作业的并发任务。但是,BeginExecute 或等效的异步方法将请求传递到异步驱动程序级别并且根本不消耗线程。这对于 Web 应用程序或高吞吐量服务器来说可能是一个巨大的好处
  • @PanagiotisKanavos 我理解,但这只是语义问题,对消费者来说并不重要——尤其是考虑到返回任务的方法的典型命名约定要求它们附加 Async,而不是并发,在这种情况下,任务是否在最低级别异步并不重要。
  • @AntP 这根本不是语义。这是一个根本性的区别,对消费者来说非常重要。这意味着Task.Run(()=&gt; cmd.ExecuteReader())await cmd.ExecuteReaderAsync() 根本不同。最后,这意味着如果您正确使用异步编程,同一台服务器可以处理 很多(倍数,而不是百分比)更多请求。它还可以生成更清洁、更安全的代码。
  • @PanagiotisKanavos 再次 - 我已经了解所有这些。我要说的是,“异步”被广泛(并且通常是明确地)用于特定的上下文(即,如果它不阻塞上下文,那么它相对于该上下文是异步的——但是这个概念是有漏洞的) )。我不确定这与“真正的”操作系统级异步之间的区别是否与这个问题有关(尽管重读它很可能是 OP 不知道的区别)。
猜你喜欢
  • 2015-05-31
  • 1970-01-01
  • 2021-01-19
  • 1970-01-01
  • 2012-04-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-05
相关资源
最近更新 更多