代码之家  ›  专栏  ›  技术社区  ›  cdonts

Blazor服务器应用程序中的Hangfire作业中未注入服务

  •  0
  • cdonts  · 技术社区  · 4 年前

    免责声明:我对C#ASP非常陌生。NET核心和依赖注入世界。我用默认模板创建了一个简单的Blazor服务器应用程序,它构建了一个模拟天气服务,并将从中获取的数据显示在一个表中。现在我希望表格每五秒钟自动更新一次,为此我使用 Hangfire.AspNetCore Hangfire.MemoryStorage 包装。所以我稍微修改了 FetchData.razor 组件的外观如下所示:

    @page "/fetchdata"
    
    @using WeatherTest.Data
    @using Hangfire
    
    @inject WeatherForecastService ForecastService
    
    <h1>Weather forecast</h1>
    
    <p>This component demonstrates fetching data from a service.</p>
    
    @if (forecasts == null)
    {
        <p><em>Loading...</em></p>
    }
    else
    {
        <table class="table">
            <thead>
                <tr>
                    <th>Date</th>
                    <th>Temp. (C)</th>
                    <th>Temp. (F)</th>
                    <th>Summary</th>
                </tr>
            </thead>
            <tbody>
                @foreach (var forecast in forecasts)
                {
                    <tr>
                        <td>@forecast.Date.ToShortDateString()</td>
                        <td>@forecast.TemperatureC</td>
                        <td>@forecast.TemperatureF</td>
                        <td>@forecast.Summary</td>
                    </tr>
                }
            </tbody>
        </table>
    }
    
    @code {
        private WeatherForecast[] forecasts;
    
        public async Task UpdateForecasts()
        {
          forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
          BackgroundJob.Schedule(() => UpdateForecasts(), TimeSpan.FromSeconds(5));
        }
    
        protected override async Task OnInitializedAsync()
        {
          await UpdateForecasts();
        }
    }
    

    这里是天气预报:

    using System;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace WeatherTest.Data
    {
        public class WeatherForecastService
        {
            private static readonly string[] Summaries = new[]
            {
                "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
            };
    
            public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
            {
                var rng = new Random();
                return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast
                {
                    Date = startDate.AddDays(index),
                    TemperatureC = rng.Next(-20, 55),
                    Summary = Summaries[rng.Next(Summaries.Length)]
                }).ToArray());
            }
        }
    }
    

    以及服务配置:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
        services.AddServerSideBlazor();
        services.AddSingleton<WeatherForecastService>();
        services.AddHangfire(c => c.UseMemoryStorage());
        services.AddHangfireServer();
    }
    

    问题是 UpdateForecasts() 从调用时成功 OnInitializedAsync() ,但在被Hangfire调用时失败,并在 forecasts = await ForecastService.GetForecastAsync(DateTime.Now); :

    系统NullReferenceException:'对象引用未设置为 对象的实例。”

    天气测试。页。获取数据。预报员服务。返回空值。

    在我看来,既然Hangfire worker运行在另一个线程中,那么 WeatherForecastService 不会被注射。我说得对吗?是否可以从多个线程使用一个单线程,或者每个线程都应该有自己的服务实例?最后,我该如何解决这个问题?

    0 回复  |  直到 4 年前
        1
  •  1
  •   MrC aka Shaun Curtis    4 年前

    这是答案 FetchData 使用标准的组件 Timer .

    有几点:

    1. 我添加了一个 Task.Delay 以模拟慢速连接并显示页面正在刷新。
    2. StateHasChanged 是这样包装的 await this.InvokeAsync(StateHasChanged) 以确保它在UI上下文线程上运行。
    3. 工具 IDisposable 因为我们需要断开计时器事件处理程序。
    @page "/fetchdata"
    @implements IDisposable
    <PageTitle>Weather forecast</PageTitle>
    
    @using StackOverflow.Server.Data
    @inject WeatherForecastService ForecastService
    
    <h1>Weather forecast</h1>
    
    <p>This component demonstrates fetching data from a service.</p>
    
    <div class="p-2">
        <button class="btn @this.btnCss" @onclick="ToggleTimer">@this.btnText</button>
    </div>
    
    @if (forecasts == null)
    {
        <p><em>Loading...</em></p>
    }
    else
    {
        <table class="table">
            <thead>
                <tr>
                    <th>Date</th>
                    <th>Temp. (C)</th>
                    <th>Temp. (F)</th>
                    <th>Summary</th>
                </tr>
            </thead>
            <tbody>
                @foreach (var forecast in forecasts)
                {
                    <tr>
                        <td>@forecast.Date.ToShortDateString()</td>
                        <td>@forecast.TemperatureC</td>
                        <td>@forecast.TemperatureF</td>
                        <td>@forecast.Summary</td>
                    </tr>
                }
            </tbody>
        </table>
    }
    
    @code {
        private System.Timers.Timer aTimer = new System.Timers.Timer(2000);
    
        private WeatherForecast[]? forecasts;
    
        private string btnCss => aTimer.Enabled ? "btn-danger" : "btn-success";
    
            private string btnText => aTimer.Enabled ? "Stop" : "Start";
    
        protected override async Task OnInitializedAsync()
        {
            aTimer.Elapsed += TimerElapsed;
            aTimer.AutoReset = true;
            aTimer.Enabled = true;
            forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
        }
    
        public async void TimerElapsed(Object? sender, System.Timers.ElapsedEventArgs e)
        {
            forecasts = null;
            await this.InvokeAsync(StateHasChanged);
            await Task.Delay(500);
            forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
            await this.InvokeAsync(StateHasChanged);
        }
    
        public void ToggleTimer()
        {
            aTimer.Enabled = !aTimer.Enabled;
            this.InvokeAsync(StateHasChanged);
        }
    
        public void Dispose()
           => aTimer!.Elapsed -= TimerElapsed;
    }