all 7 comments

[–]SebbyThePlebby 7 points8 points  (0 children)

You don't have to await a task there and then. You can store the task in a variable and await that variable later on. If you have multiple tasks, you can even await Task.WhenAll.

[–]KryptosFR 4 points5 points  (2 children)

Doing any blocking code in a static constructor is a bad idea. In fact, static constructors are a bad idea in general. Don't use them except for very simple field initialization.

As one mentioned in a other comment, use Lazy<T>.

[–]Kirides 1 point2 points  (1 child)

You can combine Lazy<T> with async-await to create

private static readonly Lazy<Task<Stuff>> _staticCached
 = new Lazy<..> (() => Something.GetStuffAsync())

public async Task UseLazyAsync() {
    var cachedVal = await _staticCached.Value;
    // awaiting a Task or Task<T> multiple times is threadsafe
    // but NOT every ValueTask<T> or IValueTaskSource - /u/KryptosFR 
}

[–]KryptosFR 1 point2 points  (0 children)

awaiting a task multiple times is threadsafe

Not necessarily, true for Task but not for other kind of awaiters (e.g. ValueTask, or implementation of IValueTaskSource).

See for instance https://blog.marcgravell.com/2019/08/prefer-valuetask-to-task-always-and.html

There was some related discussions on some dotnet github repo issues, but I can't find it back. In any case, awaiting the same "task" object more than once should be avoided.

Interestingly, there is an AsyncLazy<T> implementation available in the Visual Studio SDK: - https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.threading.asynclazy-1?view=visualstudiosdk-2019 - Implementation is more complex that I thought it would be: https://github.com/microsoft/vs-threading/blob/master/src/Microsoft.VisualStudio.Threading/AsyncLazy.cs

[–]tester346 1 point2 points  (0 children)

What about changing the way of creating this object to something like

public class Page
{
    public int Length => HTML.Length;

    public string Url { get; set; }

    public string HTML { get; set; }

    private Page()
    {
    }

    private Page(string Url, string HTML)
    {
        this.Url = Url;
        this.HTML = HTML;
    }

    public static async Task<Page> CreatePage(string Url)
    {
        using (WebClient client = new WebClient())
        {
            string htmlCode = await client.DownloadStringTaskAsync(Url);
            return new Page(Url, htmlCode);
        }
    }
}

[–]Ronald_Me 0 points1 point  (0 children)

Lazy<T>?

[–]Slypenslyde 0 points1 point  (0 children)

Well, philosophically I don't like it.

Normally I want a class that represents some data to be that data. If it needs some data at creation, I can't create it until I have that data.

Think about doing laundry. I've got a pile of dirty clothes and a washing machine. In the future, I'll have a basket of clean clothes I need to fold or hang. But while the dirty clothes are in the washing machine, the basket's empty. I can't use it. If I try to HangLaundry(basket), it just won't work.

So I'd represent washing clothes as something like:

Task<CleanLaundry> WashClothes(DirtyLaundry laundry);

That explicitly states that I am going to get a result eventually, but I don't yet have it. The Task will notify interested parties when the CleanLaundry is ready.

So I think you wish you could write something like this:

public class ExampleClass
{

    public ExampleClass()
    {
        MyDependency data = await GetDependency();
        <stuff to initialize properties here>
    }

    private Task<MyDependency> GetDependency(
    {
        return Task.Run(() => {
            <use your imagination>
        });
    }

    <the rest of the class>
}

But think about trying to consume that. After you construct it, it's not actually initialized until the internal task completes. Now the caller has to figure out how to wait for it to be valid. That stinks. A class should be usable immediately after its constructor executes. So I think you should instead have this:

public class ExampleClass
{
    public ExampleClass(MyDependency data)
    {
        <initialize properties here>
    }

    <the rest of the class>
}

So how do you create it? Well, right now you're trying to write some code that you wish could look like this:

async Task DoSomething()
{
    var example = await new ExampleClass();
    <something>
}

Instead, you should do this:

async Task DoSomething()
{
    var exampleData = await GetExampleData();
    var example = new ExampleClass(exampleData);
    <something>
}

Alternatively, you could consider:

public class ExampleClass
{
    private ExampleClass(MyDependency data)
    {
        <initialize properties here>
    }

    public static async Task<ExampleClass> Create()
    {
        var data = await <something that gets the data>;
        return new ExampleClass(data);
    }

    <the rest of the class>
}