【问题标题】:How initialize some data only if needed when there's async functions implicated仅在涉及异步函数时才需要初始化一些数据
【发布时间】:2017-03-23 11:58:09
【问题描述】:

我的网站仅在应用程序需要时才需要初始化一些数据,并且只能执行一次。初始化必须调用 API 来获取数据。

所以基本上,我想我需要制作一个 lazy singleton,它会在第一次访问时调用 api。但是当我使用 HttpClient 类中的异步方法 GetAsync 时遇到了问题。调用 API 时发生了崩溃,但我的 TryCatch 没有捕获到异常。

我尝试将Piv GetPiv() 转换为Task<Piv> GetPiv(),但我不知道如何将它与 Lazy 对象一起使用。

public class PivHelper
{
    private static readonly Lazy<Piv> _lazyPiv = new Lazy<Piv>(GetPiv);

    public string Header()
    {
        return _lazyPiv.Value.Header;
    }

    public string Footer()
    {
        return _lazyPiv.Value.Footer;
    }

    public string Scripts()
    {
        return _lazyPiv.Value.Scripts;
    }

    public string Styles()
    {
        return _lazyPiv.Value.Styles;
    }

    private static Piv GetPiv()
    {
        try
        {
            var client = new HttpClient();

            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            HttpResponseMessage response = client.GetAsync("http://some-web-site.com").Result;

            if (response.IsSuccessStatusCode)
            {
                return response.Content.ReadAsAsync<Piv>().Result;
            }
            else
            {
                return new Piv();
            }
        }
        catch (Exception ex)
        {
            return new Piv();
        }
    }
}

public class Piv
{
    public string Header { get; set; }
    public string Footer { get; set; }
    public string Styles { get; set; }
    public string Scripts { get; set; }
}

public static class MtoHelper
{
    private static readonly Lazy<PivHelper> lazyPivHelper = new Lazy<PivHelper>(() => new PivHelper());

    public static PivHelper Piv
    {
        get
        {
            return lazyPivHelper.Value;
        }
    }
}

我的 _Layout.cshtml 文件

<!DOCTYPE html>
<html>
<head>
    <!-- Instead of using our own copy of the piv.css, we use the one
         coming from the webservices -->
    <!--<link href="~/content/piv.css" rel="stylesheet" type="text/css">-->
    @MtoHelper.Piv.Styles()

    <link rel="shortcut icon" href="/favicon.ico" />
</head>

<body>
    <header id="pageHeaderContainer" class="container">
        @MtoHelper.Piv.Header()
    </header>

    <section id="main" class="container">
        @RenderBody()
    </section>

    <footer id="pageFooterContainer" class="container">
        @MtoHelper.Piv.Footer()
    </footer>

    @MtoHelper.Piv.Scripts()
</body>
</html>

【问题讨论】:

  • 调用.Result 通常是个坏主意。你可以让GetPiv()异步吗? public static async Task&lt;Piv&gt; GetPiv() ?然后你可以await其中调用的异步操作。
  • 这是我试图做的,但是,如果 GetPiv 是一个任务 ,我如何使用 Lazy 对象。这就是我卡住的地方。
  • 公平的问题。这看起来很有帮助:blogs.msdn.microsoft.com/pfxteam/2011/01/15/asynclazyt

标签: c# async-await singleton lazy-initialization


【解决方案1】:

您需要使用asyncawait 关键字。使用 .Result 会导致问题。您可以实现如下所示的 AsyncLazy 类,这将允许您确保对 GetPiv 的调用仅发生一次。

public class AsyncLazy<T> : Lazy<Task<T>>
{
    public AsyncLazy(Func<T> valueFactory) : base(() => Task.Run(valueFactory))
    {
    }

    public AsyncLazy(Func<Task<T>> taskFactory) : base(() => Task.Run(taskFactory))
    {
    }
}

那么你就必须修改你的代码来使用前面提到的关键字:

public class PivHelper
{
    static readonly AsyncLazy<Piv> _lazyPiv = new AsyncLazy<Piv>(() => GetPivAsync());

    public Task<Piv> PivTask => _lazyPiv.Value;

    static Task<Piv> GetPivAsync()
    {
        try
        {
            using (var client = new HttpClient())
            {
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                var response = await client.GetAsync("http://some-web-site.com");

                if (response.IsSuccessStatusCode)
                {
                    return await response.Content.ReadAsAsync<Piv>();
                }
                else
                {
                    return new Piv();
                }
            }
        }
        catch (Exception ex)
        {
            return new Piv();
        }
    }
}

public class Piv
{
    public string Header { get; set; }
    public string Footer { get; set; }
    public string Styles { get; set; }
    public string Scripts { get; set; }
}

然后在您的 API 代码中,您可以像这样使用帮助程序(我对使用 ASP.NET Core 的 Web API 做了一些假设):

public class PivController : Controller
{
    private readonly PivHelper _helper;

    public PivController (PivHelper pivHelper)
    {
        _helper = pivHelper;
    }

    public async Task<IActionResult> GetPiv()
    {
        var piv = await _helper.PivTask;
        // Act on the piv, or simply return it...
    }
}

更新

根据 OP 编辑​​,我需要更新我的答案。

您错误地执行 MVC。整个想法是在遵循 SOLID 等原则方面分离关注点并促进最佳实践。你的视图不应该知道PivHelper,而是Piv模型,你的控制器应该知道PivHelper,因为它是Piv.

就需要将模型作为布局的一部分而言,这是一场不同的战斗。我建议不要这样做,并建议您将布局保留为仅指向 .css 的链接和 .js 的脚本标签,并使用最少的元素。

【讨论】:

  • 我在 MVC5 项目中使用 _layour.cshtml 文件中的结果。事实上,我在描述中的例子是真实事物的一个较小版本。 public string Header() 实际上是 public MvcHtmlString Header(),所以我可以直接在我的视图中使用它。所以它不能是一个 Task
  • 当然可以,向我展示与视图相对应的控制器操作,我将更新我的答案,说明它是如何完成的。
  • 我已经更新了关于如何完成布局的描述。我的控制器不知道 PivHelper。只有布局文件使用它。
  • 我认为这取决于您如何看待我需要解决的问题。一开始,我们在项目中使用了硬编码的css、脚本、页眉和页脚html。这是每个内部网站都需要使用的页眉和页脚。相反,我们放置了一个 Web 服务,为每个网站提供正确的页眉和页脚以供打印。所以这只是我们如何管理打印一般东西的替代品。例如。我们不写&lt;link href="~/Content/piv.css" rel="stylesheet" type="text/css"&gt;,而是打印@MtoHelper.Piv.Styles
猜你喜欢
  • 2018-06-04
  • 1970-01-01
  • 1970-01-01
  • 2021-04-08
  • 1970-01-01
  • 2022-01-25
  • 2017-05-15
  • 2015-11-05
  • 1970-01-01
相关资源
最近更新 更多