I saw this example at the end of Stephen\'s book.
This code can be accessed by more than one thread.
static int _simpleValue;
static readonly La
what is the problem with "different thread types that may call Value" in the first code?
There in nothing wrong with that code. But, imagine you had some CPU bound work along with the async
initialization call. Picture it like this for example:
static readonly Lazy<Task<int>> MySharedAsyncInteger = new Lazy<Task<int>>(
async () =>
{
int i = 0;
while (i < 5)
{
Thread.Sleep(500);
i++;
}
await Task.Delay(TimeSpan.FromSeconds(2));
return 0;
});
Now, you aren't "guarded" against these kind of operations. I'm assuming Stephan mentioned the UI thread because you shouldn't be doing any operation that's longer than 50ms on it. You don't want your UI thread to freeze, ever.
When you use Task.Run
to invoke the delegate, you're covering yourself from places where one might pass a long running delegate to your Lazy<T>
.
Stephan Toub talks about this in AsyncLazy:
Here we have a new
AsyncLazy<T>
that derives fromLazy<Task<T>>
and provides two constructors. Each of the constructors takes a function from the caller, just as doesLazy<T>
. The first constructor, in fact, takes the same Func thatLazy<T>
. Instead of passing thatFunc<T>
directly down to the base constructor, however, we instead pass down a newFunc<Task<T>>
which simply uses StartNew to run the user-providedFunc<T>
. The second constructor is a bit more fancy. Rather than taking aFunc<T>
, it takes aFunc<Task<T>>
. With this function, we have two good options for how to deal with it. The first is simply to pass the function straight down to the base constructor, e.g:
public AsyncLazy(Func<Task<T>> taskFactory) : base(taskFactory) { }
That option works, but it means that when a user accesses the Value property of this instance, the taskFactory delegate will be invoked synchronously. That could be perfectly reasonable if the
taskFactory
delegate does very little work before returning the task instance. If, however, thetaskFactory
delegate does any non-negligable work, a call to Value would block until the call totaskFactory
completes. To cover that case, the second approach is to run thetaskFactory
usingTask.Factory.StartNew
, i.e. to run the delegate itself asynchronously, just as with the first constructor, even though this delegate already returns aTask<T>
.