set.seed(1234)
Date <- seq(as.Date("2001/08/01"), by = "month", length.out = 60)
AHD <- rnorm(60, mean = 12, sd = 1)
df <- data.frame(Date=Date, AHD = AHD)
书上写着:
但是存在细微的风险。由于Lambda表达式是异步的, 它可以在任何调用Value和表达式的线程上执行 将在上下文中运行。更好的解决方案是将 基础任务中的表达式,它将强制异步 在线程池线程上执行。
我看不出当前代码有什么风险?
是否可以防止死锁,以防代码在UI线程上运行并像这样被显式等待:
class Laziness
{
static string cmdText = null;
static SqlConnection conn = null;
Lazy<Task<Person>> person =
new Lazy<Task<Person>>(async () =>
{
using (var cmd = new SqlCommand(cmdText, conn))
using (var reader = await cmd.ExecuteReaderAsync())
{
if (await reader.ReadAsync())
{
string firstName = reader["first_name"].ToString();
string lastName = reader["last_name"].ToString();
return new Person(firstName, lastName);
}
}
throw new Exception("Failed to fetch Person");
});
public async Task<Person> FetchPerson()
{
return await person.Value;
}
}
答案 0 :(得分:3)
我简化了您的示例,以显示每种情况下会发生什么。在第一种情况下,Task
使用async
lambda创建:
Lazy<Task<string>> myLazy = new Lazy<Task<string>>(async () =>
{
string result = $"Before Delay: #{Thread.CurrentThread.ManagedThreadId}";
await Task.Delay(100);
return result += $", After Delay: #{Thread.CurrentThread.ManagedThreadId}";
});
private async void Button1_Click(object sender, EventArgs e)
{
int t1 = Thread.CurrentThread.ManagedThreadId;
var result = await myLazy.Value;
int t2 = Thread.CurrentThread.ManagedThreadId;
MessageBox.Show($"Before await: #{t1}, {result}, After await: #{t2}");
}
我使用一个按钮将这段代码嵌入到新的Windows Forms应用程序中,然后单击该按钮会弹出此消息:
在等待之前:#1,在延迟之前:#1,在延迟之后:#1,在等待之后:#1
然后我将valueFactory
参数改为使用Task.Run
:
Lazy<Task<string>> myLazy = new Lazy<Task<string>>(() => Task.Run(async () =>
{
string result = $"Before Delay: #{Thread.CurrentThread.ManagedThreadId}";
await Task.Delay(100);
return result += $", After Delay: #{Thread.CurrentThread.ManagedThreadId}";
}));
现在的消息是这样:
在等待之前:#1,在延迟之前:#3,在延迟之后:#4,在等待之后:#1
因此,不使用Task.Run
意味着await
之前,之间和之后的代码将在UI线程中运行。除非在某处隐藏了CPU密集型或IO阻止代码,否则这可能并不重要。例如,Person
类的构造函数可能看起来很无辜,可能包含对Web API的一些调用。通过使用Task.Run
,您可以确保Lazy
类的初始化在完成之前不会碰到UI线程。
答案 1 :(得分:1)
我看不出当前代码有什么风险?
对我来说,主要问题是异步初始化委托不知道它将在什么上下文/线程上运行,并且上下文/线程可以根据比赛条件而有所不同。例如,如果UI线程和线程池线程都尝试同时访问Value
,则在某些执行中,委托将在UI上下文中运行,而在其他执行中,委托将在线程池上下文中运行。 。在ASP.NET(预核心)世界中,它可能会变得有些棘手:委托有可能捕获某个请求的请求上下文,然后将该请求上下文取消(并释放),然后尝试在该上下文上恢复,不漂亮。
都没关系。但是在某些情况下,可能会发生坏事。引入Task.Run
只会消除这种不确定性:委托将始终在线程池线程上没有上下文的情况下运行。