NancyFx + SignalR +使用Progress <t> </t>进行异步进度报告

时间:2014-08-06 18:37:27

标签: c# asp.net signalr async-await nancy

我半满欲望地转向你......我一直在尝试使用SignalR,.net 4.5中的Async和Progress来构建一个NancyFx应用程序的进度报告。我的主要灵感来自这篇文章:https://blog.safaribooksonline.com/2014/02/06/server-side-signalr/

我已经对代码进行了一些定制,使其也支持进度报告,而不仅仅是百分比。

问题在于:我有一个名为ReviewSetup.SetupAsync(..,...,IProgress progress)的繁重任务,使用ProgressJobManager.Instance.DoJobAsync方法从Nancy模块调用。我传入IProgress作为SetupAsync方法的最后一个参数。我为Progress.ProgressChanged设置了一个EventHandler,每当我从SetupAsync()方法中报告一些进展时都应该调用它。

正如您在下面的代码中看到的,我在ProgressJobManager.Instance.DoJobAsync()方法中连接了事件处理程序,这可能是事情开始出错的地方之一,ExecutionContext-wise / thread-wise。

Progress的ProgressChanged事件的EventHandler反过来通过调用ReportStep()方法更新ProgressJob。永远不会调用这些方法,因为它们在我设置的任何断点上都不会中断,我认为这也是由于在不同的线程上执行的SetupAsync()方法,因此无法使用提供的报告参数IProgress进度......

没有正确调用进度报告的这个问题级联到ProgressJob.StepAdded事件没有被引发,因此没有被JobManger捕获,因此无法告诉ProgressHub向客户端发送消息,通知他们的进展更新。

上面提到的文章要求在ProgressJobManager.Instance.DoJobAsync()方法中完成所有繁重的工作(以及报告的进度报告)。这很好,但我想调用ReviewSetup.SetupAsync()方法并让THAT方法报告进度...我希望你们还在我身边:-)。我真的想要了解.net 4.5中的异步。我可能有点想要将它构建成一个更复杂的应用程序,但这是在我和我的自我之间: - )。

无论如何,我的问题是:

  • 我是否以完全错误的方式接近过这个?
  • 这是一个ExecutionContext / thread问题,由于我在所有async / await方面的经验很容易通过更好地理解.net 4.5中async的工作原理来解决吗?如果是这样,请指出我正确的方向! : - )

抱歉,这不是您的花园种类&#34;我如何转换为问题类型。因此,您的帮助甚至比平常更受欢迎! : - )

这是我的代码:

ReviewSetup.cs(执行实际报告的类):

public class ReviewSetup
{

//other stuff here

    public async Task SetupAsync(ReviewSetupConfiguration setupConfiguration, string    notebookGuid, List<string> tagGuids, ReflectUser user, string reviewName, IProgress<ProgressStatus> progress)
    {
        NoteFilter = notebookGuid != "0" ? new NoteFilter() { NotebookGuid = notebookGuid, TagGuids = tagGuids } : new NoteFilter() { TagGuids = tagGuids };
        Name = reviewName;
        _user = user;
        UserId = user.Id;
        ReviewSetupConfiguration = setupConfiguration;
        await Task.Run(() =>
        {
            progress.Report(new ProgressStatus() { Step = "Searching for notes. This might take a few moments..." });
            GetBaseNoteList();
            progress.Report(new ProgressStatus() { Step = BaseNoteList.Notes.Count + " notes found." });
            if (NoteFilter.NotebookGuid != null)
            {
                progress.Report(new ProgressStatus() { Step = "Getting notebook name..." });
                GetNotebookName();
                progress.Report(new ProgressStatus() { Step = "Notebook name found: " + NotebookName });
            }
            if (NoteFilter.TagGuids != null)
            {
                progress.Report(new ProgressStatus() { Step = "Getting tag names..." });
                GetTagNames();
                progress.Report(new ProgressStatus() { Step = "Tag names found: " + string.Join(", ", TagNames.ToArray()) });
            }
            progress.Report(new ProgressStatus() { Step = "Calculating the number of reviews required for note selection..." });
            CalcNumberOfReviewsNeccesary(BaseNoteList.Notes.Count);
            progress.Report(new ProgressStatus() { Step = "Number of reviews required: " + _totalNrOfReviews });
            progress.Report(new ProgressStatus() { Step = "Generating review schedule..." });
            BuildMasterNoteList();
            Id = _reviewSetupService.Insert(this); //must insert here because we need a database id when generating the reviews.
            GenerateReviews();
            _reviewSetupService.Save(this); //call Update() to persist the already inserted ReviewSetup object with the generated reviews.
            progress.Report(new ProgressStatus() { Step = "Successfully completed filter setup.", IsComplete = true });
        });
    }
}

ReviewSetupApiModule.cs(接收请求的Nancy模块和callin DoJobAsync())

        Post["/newasync"] = parameters =>
        {
            this.RequiresAuthentication();
            try
            {

                //removed some stuff

                var progresss = new Progress<ProgressStatus>();
                var job = ProgressJobManager.Instance.DoJobAsync(j =>
                {                        
                    //EventHandler bound at the wrong moment/on the wrong thread?
                    progresss.ProgressChanged += delegate(object sender, ProgressStatus status)
                    {
                        j.ReportStep(status.Step);
                        if (status.IsComplete == true)
                        {
                            j.ReportComplete();
                        }
                    };   
                    setup.Setup(config, setupModel.NotebookGuid, setupModel.TagGuids, user, setupModel.ReviewName, progresss);
                });
                //this method needs to keep executing so it can return the job.Id, needed by the client to track its progress.
                return Response.AsJson(job).WithStatusCode(HttpStatusCode.OK);
            }
            catch (Exception ex)
            {
                return HttpStatusCode.BadRequest;
            }
        };

ProgressJob.cs

public class ProgressJob
{
    public event EventHandler<EventArgs> ProgressChanged;
    public event EventHandler<EventArgs> StepAdded;
    public event EventHandler<EventArgs> Completed;

    private volatile int _progress;
    private volatile bool _completed;
    private volatile List<string> _steps;
    private CancellationTokenSource _cancellationTokenSource;

    public List<string> Steps
    {
        get
        {
            return _steps;
        }
    }

    public ProgressJob(string id)
    {
        Id = id;
        _cancellationTokenSource = new CancellationTokenSource();
        _steps = new List<string>();
        _completed = false;
    }

    public string Id { get; private set; }

    public int Progress
    {
        get { return _progress; }
    }

    public bool IsComplete
    {
        get { return _completed; }
    }

    public CancellationToken CancellationToken
    {
        get { return _cancellationTokenSource.Token; }
    }

    public Progress<ProgressStatus> ProgressStatus { get; set; } 

    public void ReportProgress(int progress)
    {
        if (_progress != progress)
        {
            _progress = progress;
            OnProgressChanged();
        }
    }

    public void ReportStep(string step)
    {
        _steps.Add(step);
        OnStepAdded();
    }

    public void ReportComplete()
    {
        if (!IsComplete)
        {
            _completed = true;
            OnCompleted();
        }
    }

    protected virtual void OnCompleted()
    {
        var handler = Completed;
        if (handler != null) handler(this, EventArgs.Empty);
    }

    protected virtual void OnProgressChanged()
    {
        var handler = ProgressChanged;
        if (handler != null) handler(this, EventArgs.Empty);
    }

    protected virtual void OnStepAdded()
    {
        var handler = StepAdded;
        if (handler != null) handler(this, EventArgs.Empty);
    }

    public void Cancel()
    {
        _cancellationTokenSource.Cancel();
    }
}

ProgressJobManager.cs:

public class ProgressJobManager
{
    public static ProgressJobManager Instance;
    private IHubContext _hubContext;
    private IProgressJobDataProvider _jobDataProvider;

    public ProgressJobManager(IProgressJobDataProvider jobDataProvider)
    {
        _jobDataProvider = jobDataProvider;
        _hubContext = GlobalHost.ConnectionManager.GetHubContext<ProgressHub>();
    }

    public ProgressJob DoJobAsync(Action<ProgressJob> action)
    {
        var job = new ProgressJob(Guid.NewGuid().ToString());
        _jobDataProvider.AddJob(job);
        job.ProgressStatus = new Progress<ProgressStatus>();
        Task.Factory.StartNew(() =>
        {
            action(job);
            job.ReportComplete();
            job = _jobDataProvider.DeleteJob(job);
        },
        TaskCreationOptions.LongRunning);
        BroadcastJobStatus(job);
        return job;
    }

    private void BroadcastJobStatus(ProgressJob job)
    {
        job.ProgressChanged += HandleProgressChanged;
        job.Completed += HandleJobCompleted;
        job.StepAdded += HandleStepAdded;
    }

    private void HandleJobCompleted(object sender, EventArgs e)
    {
        var job = (ProgressJob)sender;

        _hubContext.Clients.Group(job.Id).JobCompleted(job.Id);

        job.ProgressChanged -= HandleProgressChanged;
        job.Completed -= HandleJobCompleted;
        job.StepAdded -= HandleStepAdded;
    }

    private void HandleProgressChanged(object sender, EventArgs e)
    {
        var job = (ProgressJob)sender;
        _hubContext.Clients.Group(job.Id).ProgressChanged(job.Id, job.Progress);
    }

    private void HandleStepAdded(object sender, EventArgs e)
    {
        var job = (ProgressJob)sender;
        var step = job.Steps[job.Steps.Count - 1];
        _hubContext.Clients.Group(job.Id).StepAdded(job.Id, step);
    }

    public ProgressJob GetJob(string id)
    {
        return _jobDataProvider.GetJob(id);
    }
}

ProgressHub.cs:

public class ProgressHub : Hub
{
    public void TrackJob(string jobId)
    {
        Groups.Add(Context.ConnectionId, jobId);       
    }

    public void CancelJob(string jobId)
    {
        var job = ProgressJobManager.Instance.GetJob(jobId);
        if (job != null)
        {
            job.Cancel();
        }
    }

    public void ProgressChanged(string jobId, int progress)
    {
        Clients.Group(jobId).ProgressChanged(jobId, progress);
    }

    public void JobCompleted(string jobId)
    {
        Clients.Group(jobId).JobCompleted(jobId);
    }

    public void StepAdded(string jobId, string step)
    {
        Clients.Group(jobId).StepAdded(jobId, step);
    }
}

最后,ProgressStatus.cs:

public class ProgressStatus
{
    public bool IsComplete { get; set; }
    public int Percentage { get; set; }
    public string Step { get; set; }
}

1 个答案:

答案 0 :(得分:0)

快速浏览代码表明问题出现在ReviewSetupApiModule.cs中,您可以在其中调用setup.Setup但不要await它返回的任务。第二个问题(可能是问题中的拼写错误)是ReviewSetup.cs中的方法称为SetupAsync。

当你没有await任务时,调用代码将在任务完成之前继续运行。在这种情况下,这将导致JobManager认为Job已完成,并将其从列表中删除。