我将在负载测试中使用此方法,这意味着可能会从不同的线程快速地进行数千次调用。我想知道我是否必须考虑在随后的调用中会发生什么,在那里创建一个新的 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();
}
}
}
我使用术语可重入来描述这个方法将被一个或多个线程调用的事实。
最佳答案
因此,我们想知道在多线程上下文中使用此方法可能会出现哪些潜在问题,无论是通过在具有多个线程的环境中进行单个调用,还是通过一个或多个线程进行多个调用。
首先要看的是这个方法对外暴露了什么。如果我们正在设计这个方法,我们可以控制它做什么,但不能控制调用者做什么。我们需要假设任何人都可以对传递给我们的方法的任何内容执行任何操作,对返回值执行的操作以及对调用该类的类型/对象实例执行的操作。让我们依次看看这些。
网址:
显然,调用者可以传入一个无效的 URL,但这不是异步或多线程特有的问题。他们真的不能用这个参数做任何其他事情。在将字符串传递给我们之后,他们不能从另一个线程改变字符串,因为 string
是不可变的(或者至少在外部是不可变的)。
返回值:
所以乍一看,这实际上似乎是一个问题。我们正在返回一个对象实例(一个 Task
);该对象正在被我们正在编写的这个方法改变(将它标记为错误、异常、完成)并且它也可能被这个方法的调用者改变(以添加延续)。这个 Task
最终从多个不同的线程发生变异也是很有可能的(任务可以传递给任意数量的其他线程,这些线程可以通过添加延续来改变它,或者在我们改变它时读取值)。
幸运的是,Task
是专门为支持所有这些情况而设计的,并且由于它在内部执行的同步,它将正常运行。作为这个方法的作者,我们不需要关心谁向我们的任务添加了什么延续,从什么线程,不同的人是否同时添加它们,事情发生的顺序是什么,延续是在之前还是之前添加的在我们将任务标记为已完成或其中任何一项之后。虽然任务可以从外部改变,即使是从其他线程,但从这个方法中,他们无法做任何我们可以观察到的事情。同样,无论我们做什么,它们的延续都会正常运行。它们的延续总是会在任务被标记为完成后的一段时间内触发,或者如果它已经完成则立即触发。它没有基于事件的模型在触发事件以表示完成后添加事件处理程序时可能出现的竞争条件。
最后,我们有类型/实例的状态。
这个很容易。这是一个 static
方法,因此即使我们想访问,也没有我们可以访问的实例字段。该方法也没有访问静态字段,因此线程之间没有共享我们需要关注的状态。
除了字符串输入和任务输出之外,此方法使用的状态完全是本地变量,在此方法之外永远无法访问。由于此方法在单个线程中完成所有操作(如果有同步上下文,或者即使使用线程池线程,它也至少按顺序执行所有操作),因此我们无需担心任何内部线程问题,只需担心可能发生的问题由调用者在外部发生。
当您担心在之前的调用完成之前多次调用方法时,这里主要关注的是对字段的访问。如果该方法正在访问实例/静态字段,那么不仅需要考虑使用任何给定输入状态调用方法的含义,还需要考虑其他方法同时访问这些字段时发生的情况。由于我们没有访问,因此对于此方法没有实际意义。
关于c# - 在扩展方法中使用 await 运算符时是否需要考虑可能的重入编码问题?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28883642/