我半满欲望地转向你......我一直在尝试使用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中的异步。我可能有点想要将它构建成一个更复杂的应用程序,但这是在我和我的自我之间: - )。
无论如何,我的问题是:
抱歉,这不是您的花园种类"我如何转换为问题类型。因此,您的帮助甚至比平常更受欢迎! : - )
这是我的代码:
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; }
}
答案 0 :(得分:0)
快速浏览代码表明问题出现在ReviewSetupApiModule.cs中,您可以在其中调用setup.Setup
但不要await
它返回的任务。第二个问题(可能是问题中的拼写错误)是ReviewSetup.cs中的方法称为SetupAsync。
当你没有await
任务时,调用代码将在任务完成之前继续运行。在这种情况下,这将导致JobManager认为Job已完成,并将其从列表中删除。