我需要在任务中做一个工作(用于监控的无限循环(,但我如何获得这项工作的结果?
我做这些事情的逻辑我错了?我认为这是一个范围问题。
有一个简化的示例: 变量是"第一个",我想要"编辑">
namespace my{
public class Program{
public static void Main(string[] args){
Logic p = new Logic();
Task t = new Task(p.process);
t.Start();
Console.WriteLine(p.getVar());// result="first"
}
}
public class Logic{
public string test = "first";
public void process(){
while(true){
//If condition here
this.test = "edit";
}
}
public String getVar(){
return this.test;
}
}
}
可以使用自定义event
来完成。在您的情况下,它可以是这样的:
public event Action<string> OnValueChanged;
然后附加到它
p.OnValueChanged += (newValue) => Console.WriteLine(newValue);
并且不要忘记发射它
this.test = "edit";
OnValueChanged?.Invoke(this.test);
任务不是线程,它们不需要.Start
调用即可启动它们。所有示例和教程都显示了使用Task.Run
或Task.StartNew
是有原因的 - 任务是函数将在将来某个时候执行并产生结果的承诺。当任务计划程序决定它们应该运行时,它们将在从 ThreadPool 拉取的线程上运行。创建冷任务和调用.Start
并不能保证它们会启动,它只会使代码更难阅读。
在最简单的情况下,轮询(例如远程 HTTP 端点(可以像以下那样简单:
public static async Task Main()
{
var client=new HttpClient(serverUrl);
while(true)
{
var response=await client.GetAsync(relativeServiceUrl);
if(!response.IsSuccessStatusCode)
{
//That was an error, do something with it
}
await Task.Delay(1000);
}
}
无需启动新任务,因为GetAsync
是异步的。WCF 和 ADO.NET 还提供异步执行方法。
如果没有要调用的异步方法,或者我们需要在异步调用之前执行一些繁重的工作,则可以使用 Task.Run 并行启动方法并等待它完成:
public bool CheckThatService(string serviceUrl)
{
....
}
public static async Task Main()
{
var url="...";
//...
while(true)
{
var ok=Task.Run(()=>CheckThatService(url));
if(!ok)
{
//That was an error, do something with it
}
await Task.Delay(1000);
}
}
如果我们想并行测试多个系统怎么办?我们可以并行启动多个任务,等待所有任务完成并检查其结果:
public static async Task Main()
{
var urls=new[]{"...","..."};
//...
while(true)
{
var tasks=urls.Select(url=>Task.Run(()=>CheckThatService(url));
var responses=await Task.WhenAll(tasks);
foreach(var response in responses)
{
///Check the value, due something
}
await Task.Delay(1000);
}
}
Task.WhenAll
返回一个数组,其中结果按任务的创建顺序排列。这允许检查索引以查找原始 URL。更好的主意是一起返回结果和 url,例如使用元组:
public static (bool ok,string url) CheckThatService(string serviceUrl)
{
....
return (true,url);
}
代码不会有太大变化:
var tasks=urls.Select(url=>Task.Run(()=>CheckThatService(url));
var responses=await Task.WhenAll(tasks);
foreach(var response in responses.Where(resp=>!resp.ok))
{
///Check the value, due something
}
如果我们想存储所有调用的结果怎么办?我们不能使用列表或队列,因为它们不是线程安全的。我们可以改用 ConcurrentQueue:
ConcurrentQueue<string> _results=new ConcurrentQueue<string>();
public static (bool ok,string url) CheckThatService(string serviceUrl)
{
....
_results.Enqueue(someresult);
return (true,url);
}
如果我们想定期报告进度,可以使用IProgress<T>
,如在异步 API 中启用进度和取消中所示。
我们可以将所有监视代码放在一个单独的方法/类中,该方法/类接受带有进度对象的IProgress< T>
参数,该参数可以报告成功、错误消息和导致它们的 URL,例如:
class MonitorDTO
{
public string Url{get;set;}
public bool Success{get;set;}
public string Message{get;set;}
public MonitorDTO(string ulr,bool success,string msg)
{
//...
}
}
class MyMonitor
{
string[] _urls=url;
public MyMonitor(string[] urls)
{
_urls=url;
}
public Task Run(IProgress<MonitorDTO> progress)
{
while(true)
{
var ok=Task.Run(()=>CheckThatService(url));
if(!ok)
{
_progress.Report(new MonitorDTO(ok,url,"some message");
}
await Task.Delay(1000);
}
}
}
此类可以按以下方式使用:
public static async Task Maim()
{
var ulrs=new[]{....};
var monitor=new MyMonitor(urls);
var progress=new Progress<MonitorDTO>(pg=>{
Console.WriteLine($"{pg.Success} for {pg.Url}: {pg.Message}");
});
await monitor.Run(progress);
}
在异步 API 中启用进度和取消演示如何使用 CancelTokenSource 实现监视类的另一个重要部分 - 取消它。监视方法可以定期检查取消令牌的状态,并在引发时停止监视:
public Task Run(IProgress<MonitorDTO> progress,CancellationToken ct)
{
while(!ct.IsCancellationRequested)
{
//...
}
}
public static async Task Maim()
{
var ulrs=new[]{....};
var monitor=new MyMonitor(urls);
var progress=new Progress<MonitorDTO>(pg=>{
Console.WriteLine($"{pg.Success} for {pg.Url}: {pg.Message}");
});
var cts = new CancellationTokenSource();
//Not awaiting yet!
var monitorTask=monitor.Run(progress,cts.Token);
//Keep running until the first keypress
Console.ReadKey();
//Cancel and wait for the monitoring class to gracefully stop
cts.Cancel();
await monitorTask;
在这种情况下,当提出取消令牌时,循环将退出。通过不等待MyMonitor.Run()
我们可以继续在主线程上工作,直到发生信号监控应该停止的事件。
getVar
方法在process
方法之前执行。 请确保等到任务完成后再调用getVar
方法。
Logic p = new Logic();
Task t = new Task(p.process);
t.Start();
t.Wait(); // Add this line!
Console.WriteLine(p.getVar());
如果您想了解有关Wait
方法的更多信息,请查看此链接。