在扩展方法中使用 await 运算符时,是否需要考虑可能的重入编码问题?
Do I need to consider possible re-entrant coding issues when using the await operator in an extension method?
我将在负载测试中使用此方法,这意味着来自不同线程的数千次调用可能会很快发生。我想知道我是否必须考虑在后续调用中会发生什么情况,即创建新的 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();
}
}
}
我使用可重入一词来描述这个方法将被一个或多个线程调用这一事实。
所以我们想知道在多线程上下文中使用此方法可能会出现哪些潜在问题,无论是通过在具有多个线程的环境中的单个调用,还是从一个或多个线程进行的多个调用。
首先要看这个方法对外暴露了什么。如果我们正在设计这个方法,我们可以控制它做什么,但不能控制调用者做什么。我们需要假设任何人都可以用他们传递给我们的方法的任何东西做任何事情,他们用 returned 值做什么,以及他们用 type/object 实例做什么 class拜访。让我们依次看看这些。
URL:
显然调用者可以传入无效的 URL,但这不是异步或多线程特有的问题。他们不能用这个参数做任何其他事情。在将字符串传递给我们后,他们不能改变来自另一个线程的字符串,因为 string
是不可变的(或者至少在外部观察到的不可变)。
return值:
所以乍一看,这实际上可能是个问题。我们正在 returning 对象实例 (a Task
);该对象正在被我们正在编写的这个方法改变(将其标记为错误、异常、已完成),并且它也可能被该方法的调用者改变(以添加延续)。这个 Task
最终从多个不同的线程发生变异也是很合理的(任务可以传递给任意数量的其他线程,这些线程可以通过添加延续来改变它,或者在我们改变时读取值它)。
幸运的是,Task
是专门为支持所有这些情况而设计的,并且由于它在内部执行的同步,它将正常运行。作为这个方法的作者,我们不需要关心谁在我们的任务中添加了什么延续,从什么线程,是否不同的人同时添加它们,以什么顺序事情发生在,无论是在我们将任务标记为已完成之前还是之后添加延续,或者其中任何一个。虽然任务可以在外部发生变化,甚至可以从其他线程发生变化,但我们无法通过这种方法观察到它们可以做的任何事情。同样,无论 we 做什么,它们的延续都会正常运行。它们的延续将始终在任务标记为已完成后的某个时间触发,或者如果它已经完成则立即触发。它没有基于事件的模型在事件被触发以发出完成信号后添加事件处理程序的可能竞争条件。
最后,我们有 type/instance 的状态。
这个很简单。这是一个 static
方法,因此即使我们愿意,也没有我们可以访问的实例字段。此方法也没有访问静态字段,因此我们需要关注的线程之间没有共享状态。
除了字符串输入和任务输出之外,该方法使用的状态完全是局部变量,在该方法之外永远无法访问。由于这个方法在单个线程中完成所有事情(如果有同步上下文,或者即使使用线程池线程它至少按顺序完成所有事情),我们不必担心关于内部的任何线程问题,只有调用者在外部可能发生的事情。
当您担心方法在先前调用完成之前被多次调用时,这里主要关注的是对字段的访问。如果该方法正在访问 instance/static 字段,那么不仅需要考虑使用任何给定输入状态调用方法的含义,还需要考虑如果其他方法同时访问这些字段时发生的情况.由于我们访问 none,因此 对于此方法 没有实际意义。
我将在负载测试中使用此方法,这意味着来自不同线程的数千次调用可能会很快发生。我想知道我是否必须考虑在后续调用中会发生什么情况,即创建新的 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();
}
}
}
我使用可重入一词来描述这个方法将被一个或多个线程调用这一事实。
所以我们想知道在多线程上下文中使用此方法可能会出现哪些潜在问题,无论是通过在具有多个线程的环境中的单个调用,还是从一个或多个线程进行的多个调用。
首先要看这个方法对外暴露了什么。如果我们正在设计这个方法,我们可以控制它做什么,但不能控制调用者做什么。我们需要假设任何人都可以用他们传递给我们的方法的任何东西做任何事情,他们用 returned 值做什么,以及他们用 type/object 实例做什么 class拜访。让我们依次看看这些。
URL:
显然调用者可以传入无效的 URL,但这不是异步或多线程特有的问题。他们不能用这个参数做任何其他事情。在将字符串传递给我们后,他们不能改变来自另一个线程的字符串,因为 string
是不可变的(或者至少在外部观察到的不可变)。
return值:
所以乍一看,这实际上可能是个问题。我们正在 returning 对象实例 (a Task
);该对象正在被我们正在编写的这个方法改变(将其标记为错误、异常、已完成),并且它也可能被该方法的调用者改变(以添加延续)。这个 Task
最终从多个不同的线程发生变异也是很合理的(任务可以传递给任意数量的其他线程,这些线程可以通过添加延续来改变它,或者在我们改变时读取值它)。
幸运的是,Task
是专门为支持所有这些情况而设计的,并且由于它在内部执行的同步,它将正常运行。作为这个方法的作者,我们不需要关心谁在我们的任务中添加了什么延续,从什么线程,是否不同的人同时添加它们,以什么顺序事情发生在,无论是在我们将任务标记为已完成之前还是之后添加延续,或者其中任何一个。虽然任务可以在外部发生变化,甚至可以从其他线程发生变化,但我们无法通过这种方法观察到它们可以做的任何事情。同样,无论 we 做什么,它们的延续都会正常运行。它们的延续将始终在任务标记为已完成后的某个时间触发,或者如果它已经完成则立即触发。它没有基于事件的模型在事件被触发以发出完成信号后添加事件处理程序的可能竞争条件。
最后,我们有 type/instance 的状态。
这个很简单。这是一个 static
方法,因此即使我们愿意,也没有我们可以访问的实例字段。此方法也没有访问静态字段,因此我们需要关注的线程之间没有共享状态。
除了字符串输入和任务输出之外,该方法使用的状态完全是局部变量,在该方法之外永远无法访问。由于这个方法在单个线程中完成所有事情(如果有同步上下文,或者即使使用线程池线程它至少按顺序完成所有事情),我们不必担心关于内部的任何线程问题,只有调用者在外部可能发生的事情。
当您担心方法在先前调用完成之前被多次调用时,这里主要关注的是对字段的访问。如果该方法正在访问 instance/static 字段,那么不仅需要考虑使用任何给定输入状态调用方法的含义,还需要考虑如果其他方法同时访问这些字段时发生的情况.由于我们访问 none,因此 对于此方法 没有实际意义。