C# 对WebAPI的调用不能使用Async正确返回

C# 对WebAPI的调用不能使用Async正确返回,c#,asp.net-web-api,windows-services,C#,Asp.net Web Api,Windows Services,我已经创建了一个在客户端上运行的服务。此服务在服务器上的数据库中插入日志项。日志条目通过服务器上运行的API插入。下面是Service1类中的简化代码。该服务基于计时器,并在需要时重复运行。因此,它需要在需要时插入日志条目。i、 e.SendToServer()函数重复执行。我已经删除了计时器代码,因为它在这里不相关 public class Logs { public string param1 { get; set; } public string param2 { get; set;

我已经创建了一个在客户端上运行的服务。此服务在服务器上的数据库中插入日志项。日志条目通过服务器上运行的API插入。下面是
Service1
类中的简化代码。该服务基于计时器,并在需要时重复运行。因此,它需要在需要时插入日志条目。i、 e.
SendToServer()
函数重复执行。我已经删除了计时器代码,因为它在这里不相关

public class Logs
{
 public string param1 { get; set; }
 public string param2 { get; set; }
}

static HttpClient client = new HttpClient();
System.Timers.Timer timer = new System.Timers.Timer(); //New Edit

protected override void OnStart(string[] args)
{
 SendToServer();
 timer.Elapsed += new ElapsedEventHandler(OnElapsedTime);//New Edit
 timer.Interval = 60000; //New Edit
 timer.Enabled = true; //NewEdit
}

private void OnElapsedTime(object source, ElapsedEventArgs e)//New Edit
{
  SendToServer();
}

public void SendToServer()
{
 RunAsync().GetAwaiter().GetResult();
}

static async Task RunAsync(EventLogEntry Log)
{
 client.BaseAddress = new Uri("https://<IP>:<Port>/");
 client.DefaultRequestHeaders.Accept.Clear();
 client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

 Logs log = new Logs
 {
  param1 = "value1";
  param2 = "value2";
 };
 var url = await CreateLogsAsync(log);
}

static async Task<Uri> CreateLogsAsync(Logs log)
{
 ServicePointManager.ServerCertificateValidationCallback = delegate (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
 {
  return true;
 };
 HttpResponseMessage response = await client.PostAsync("api/Logs", new StringContent(new JavaScriptSerializer().Serialize(log), Encoding.UTF8, "application/json"));
 response.EnsureSuccessStatusCode();
 return response.Headers.Location;
}
公共类日志
{
公共字符串param1{get;set;}
公共字符串param2{get;set;}
}
静态HttpClient=新HttpClient();
System.Timers.Timer Timer=新的System.Timers.Timer()//新编辑
启动时受保护的覆盖无效(字符串[]args)
{
SendToServer();
timer.appeased+=new-ElapsedEventHandler(OnElapsedTime);//新建编辑
timer.Interval=60000;//新建编辑
timer.Enabled=true;//NewEdit
}
private void OnElapsedTime(对象源,ElapsedEventArgs e)//新建编辑
{
SendToServer();
}
public void SendToServer()
{
RunAsync().GetAwaiter().GetResult();
}
静态异步任务RunAsync(EventLogEntry日志)
{
client.BaseAddress=新Uri(“https://:/”;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(新的MediaTypeWithQualityHeaderValue(“应用程序/json”);
日志=新日志
{
param1=“value1”;
param2=“value2”;
};
var url=await CreateLogsAsync(log);
}
静态异步任务CreateLogsAsync(日志)
{
ServicePointManager.ServerCertificateValidationCallback=委托(对象发送方、X509Certificate证书、X509Chain链、SslPolicyErrors SslPolicyErrors)
{
返回true;
};
HttpResponseMessage response=await client.PostAsync(“api/Logs”,新的StringContent(新的JavaScriptSerializer().Serialize(log),Encoding.UTF8,“application/json”);
response.EnsureSuccessStatusCode();
返回response.Headers.Location;
}
当我安装服务或启动客户端计算机时。第一次数据库插入工作正常。但是,数据不会第二次插入到数据库中。当我重新启动系统时,它会再次插入第一个日志。我感觉到异步函数有问题,在插入数据后,对API的第一次调用永远不会正确返回


类似的代码在控制台应用程序中运行得非常好。

问题是,在执行以下命令后,您的Windows服务将关闭:

RunAsync().GetAwaiter().GetResult();
OnStart()
方法需要执行一些保持服务运行的操作,否则当
OnStart()
返回时,它将关闭。但是,您不能在
OnStart()
方法中放置永久循环,如
while(true)
,因为系统调用
OnStart()
作为对代码的回调,并且它希望
OnStart()
能够及时返回。类似地,启动
任务
或在
线程池
上启动任何东西都不起作用(如您所见),因为这些线程作为线程运行,后台线程在应用程序停止时自动停止。从链接:

线程可以是背景线程,也可以是前景线程。 背景线程与前景线程相同,只是 后台线程不阻止进程终止。一劳永逸 属于进程的前台线程已终止,公共 语言运行库结束该过程。任何剩余的后台线程 已停止且未完成

要解决这个问题,您需要启动一个前台线程。这里有一个非常粗略的例子,可以解决眼前的问题。你需要调整它来做你想让它做的事

using System.Threading;

public sealed class MyService : ServiceBase
{
    private Thread           _thread;
    private ManualResetEvent _shutdownEvent = new ManualResetEvent(false);

    protected override void OnStart(string[] args)
    {
        // Create the thread object and tell it to execute the Run method
        _thread = new Thread(Run);

        // Name the thread so it is identifyable
        _thread.Name = "MyService Thread";

        // Set the thread to be a foreground thread (keeps the service running)
        _thread.IsBackground = false;

        // Start the thread
        _thread.Start();
    }

    protected override void OnStop()
    {
        // Set the shutdown event to tell the foreground thread to exit
        // the while loop and return out of the Run() method
        _shutdownEvent.Set();

        // Wait for the foreground thread to exit
        _thread.Join();
    }

    private void Run()
    {
        // The while loop keeps the foreground thread active by executing
        // over and over again until Stop() sets the shutdown event, which
        // triggers the while loop to exit.
        while (!_shutdownEvent.WaitOne(1000))
        {
            // Put your logic here.
            RunAsync().GetAwaiter().GetResult();
        }
    }
}
请注意,
Run()
方法中的
while
循环将反复运行,直到设置关机事件为止(请参阅
Stop()
方法)。
WaitOne()
调用的参数以毫秒为单位。按照编码,在再次执行代码之前,
WaitOne()
调用将阻塞1秒。您需要对此进行调整,以便您的代码在需要时运行


HTH

你说得对,Matt,Windows服务没有同步上下文,因此
ConfigureAwait
帮不上忙。你的方法应该行得通。马特,我已经在我的代码中加入了你的建议。但问题并没有得到解决。它的第一次工作原理与以前类似,但第二次服务现在甚至没有启动。我已经检查过了,这是因为没有执行OnStop()方法。这可能是因为我正在使用Timer()类在1分钟后重复执行OneReleasedTime()方法。我已经编辑了代码,并用//新建编辑注释进行了标记。你能建议把服务关闭声明放在其他地方吗?@Faheem,不幸的是,你遇到了和以前一样的问题
SendToServer
在线程池(后台线程)上运行任务,计时器也在线程池(后台线程)上执行
appead
事件。您的方法中没有创建前台线程的内容,该线程将在
OnStart()
返回后保持服务运行。尝试实现我提供的示例,但将调用
WaitOne()
1000
更改为
60000
,因为您只希望任务每分钟运行一次。@MattDavis,增加时间后行为没有变化。只是由于增加了等待时间,数据在1分钟后显示在服务器上的数据库中。@Faheem,您是在运行我的示例,还是仍在对您的示例进行更改?更新的代码不会保持服务运行,因为它不会在
OnStart()
方法中创建任何前台线程。如果您正在运行我的示例,那么在第二次尝试数据库更新时可能会发生异常。您可以调试服务以查看发生了什么。