在扩展方法中使用wait运算符时,我是否需要考虑可能的再入编码问题

本文关键字:问题 编码 是否 方法 扩展 wait 运算符 | 更新日期: 2024-10-23 15:10:02

我将在负载测试中使用此方法,这意味着数千个调用可能会很快从不同的线程进行。我想知道我是否必须考虑在随后的呼叫中会发生什么,在之前的等待完成之前创建了一个新的WebClient?

    public static async Task<string> SendRequest(this string url)
    {
        using (var wc = new WebClient())
        {
            var bytes = await wc.DownloadDataTaskAsync(url);
            using (var reader = new StreamReader(new MemoryStream(bytes)))
            {
                return await reader.ReadToEndAsync();
            }
        }
    }

我使用术语可重入来描述这个方法将由一个或多个线程调用的事实。

在扩展方法中使用wait运算符时,我是否需要考虑可能的再入编码问题

因此,我们想知道在多线程上下文中使用此方法可能会出现什么潜在问题,无论是在具有多个线程的环境中通过单个调用,还是在从一个或多个线程进行多个调用的情况下。

首先要看的是这个方法对外公开了什么。如果我们在设计这个方法,我们可以控制它的作用,但不能控制调用者的作用。我们需要假设任何人都可以对他们传递到我们方法中的任何东西做任何事情,他们对返回值做什么,以及他们对调用类的类型/对象实例做什么。让我们依次来看这些。

网址:

显然,调用者可以传入无效的URL,但这不是异步或多线程特有的问题。他们真的不能用这个参数做任何其他事情。在将字符串传递给我们之后,他们不能对来自另一个线程的字符串进行变异,因为string是不可变的(或者至少在外部是明显不可变的)。

返回值:

所以乍一看,这实际上可能是个问题。我们正在返回一个对象实例(一个Task);我们正在编写的这个方法正在对该对象进行变异(将其标记为故障、异常、已完成),并且该对象也可能被这个方法的调用方变异(添加延续)。这个Task最终会从多个不同的线程中变异,这也是很有可能的(任务可以传递给任何数量的其他线程,这些线程可以通过添加延续来变异它,或者在我们变异它时读取值)。

幸运的是,Task是专门为支持所有这些情况而设计的,由于它在内部执行同步,它将正常工作。作为这个方法的作者,我们不需要关心谁向我们的任务添加了哪些延续,从哪个线程添加,不同的人是否同时添加,事情发生的顺序,是在我们将任务标记为完成之前还是之后添加延续,等等。虽然任务可以从外部进行变异,甚至可以从其他线程进行变异,但通过这种方法,我们无法观察到它们所能做的任何事情。同样,无论我们做什么,他们的延续都会正常工作。他们的延续总是在任务标记为完成后的一段时间内启动,或者在任务已经完成的情况下立即启动。它不具有基于事件的模型所具有的在事件触发后添加事件处理程序以表示完成的可能竞争条件。

最后,我们得到了类型/实例的状态。

这个很简单。它是一个static方法,所以即使我们想访问,也没有实例字段可以访问。这个方法也没有静态字段可以访问,所以线程之间没有状态共享,这是我们需要关注的。

除了字符串输入和任务输出之外,此方法使用的状态完全是本地变量,在该方法之外永远无法访问这些变量。由于此方法在单个线程中执行所有操作(如果有同步上下文,或者即使使用线程池线程,它也至少按顺序执行所有操作),因此我们不需要担心内部的任何线程问题,只需要担心调用方在外部可能发生的情况。

当您担心在之前的调用完成之前多次调用方法时,这里主要关注的是对字段的访问。如果该方法正在访问实例/静态字段,那么不仅需要考虑在任何给定输入状态下调用方法的含义,还需要考虑如果其他方法同时访问这些字段会发生什么。由于我们访问none,因此对于这个方法来说,这是没有意义的。