在 NancyFX 自主机单例中调用关闭任务
Calling Shutdown Tasks in a NancyFX self host singleton
我目前正在尝试找到一种调用任务的方法,该任务需要在关闭在 NancyFX 自托管应用程序内的 TinyIOC 中创建的单例对象时调用。
到目前为止,我还没有找到答案,而且我也愿意接受更好的想法来实现我即将描述的场景。
概述
我有一个正在开发的基于 PHP 的 Web 应用程序,因为这是 PHP,没有旋转 thread/server 坐着听和处理长时间 运行 任务,php 代码在请求它的浏览器请求的生命周期内存在。
有问题的应用程序需要向 Web 服务发出一些请求,这可能需要一些时间才能完成,因此我想到了使用 Topshelf 在 C# 中实现一些后端服务的想法, NancyFX 和 Stackexchange.Redis.
该服务是一个标准的 NancyFX 自托管控制台应用程序,如下所示:
Program.cs
using Topshelf;
namespace processor
{
public class Program
{
static void Main()
{
HostFactory.Run(x =>
{
x.UseLinuxIfAvailable();
x.Service<ServiceApp>(s =>
{
s.ConstructUsing(app => new ServiceApp());
s.WhenStarted(sa => sa.Start());
s.WhenStopped(sa => sa.Stop());
});
});
}
}
}
ServiceApp.cs
using System;
using Nancy.Hosting.Self;
namespace processor
{
class ServiceApp
{
private readonly NancyHost _server;
public ServiceApp()
{
_server = new NancyHost(new Uri(Settings.NancyUrl));
}
public void Start()
{
Console.WriteLine("processor starting.");
try
{
_server.Start();
}
catch (Exception)
{
throw new ApplicationException("ERROR: Nancy Self hosting was unable to start, Aborting.");
}
Console.WriteLine("processor has started.");
}
public void Stop()
{
Console.WriteLine("processor stopping.");
_server.Stop();
Console.WriteLine("processor has stopped.");
}
}
}
ProcessorKernel.cs
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using StackExchange.Redis;
namespace processor
{
public class ProcessorKernel
{
private readonly ConnectionMultiplexer _redis;
private ISubscriber _redisSubscriber;
public ProcessorKernel()
{
try
{
_redis = ConnectionMultiplexer.Connect(Settings.RedisHost);
}
catch (Exception ex)
{
throw new ApplicationException("ERROR: Could not connect to redis queue, aborting.");
}
RegisterProcessor();
RedisMonitor();
}
~ProcessorKernel()
{
var response = RequestDeregistration();
// Do some checks on the response here
}
private RegistrationResponse RequestRegistration()
{
string registrationResponse = string.Empty;
try
{
registrationResponse = Utils.PostData("/processorapi/register", new Dictionary<string, string>
{
{"channelname", Settings.ChannelName},
});
}
catch (ApplicationException ex)
{
throw new ApplicationException("ERROR: " + ex.Message + " occured while trying to register. Aborting");
}
return JsonConvert.DeserializeObject<RegistrationResponse>(registrationResponse);
}
private DeregistrationResponse RequestDeregistration()
{
var deregistrationResponse = "";
try
{
deregistrationResponse = Utils.PostData("/processorapi/deregister", new Dictionary<string, string>
{
{"channelname", Settings.ChannelName},
});
}
catch (ApplicationException ex)
{
throw new ApplicationException("ERROR: " + ex.Message + " occured while trying to deregister. Aborting");
}
return JsonConvert.DeserializeObject<DeregistrationResponse>(deregistrationResponse);
}
private void RegisterProcessor()
{
Console.WriteLine("Registering processor with php application");
RegistrationResponse response = RequestRegistration();
// Do some checks on the response here
Console.WriteLine("Processor Registered");
}
private void RedisMonitor(string channelName)
{
_redisSubscriber = _redis.GetSubscriber();
_redisSubscriber.Subscribe(channelName, (channel, message) =>
{
RedisMessageHandler(message);
});
}
private void RedisMessageHandler(string inboundMessage)
{
RedisRequest request = JsonConvert.DeserializeObject<RedisRequest>(inboundMessage);
// Do something with the message here
}
}
}
除了这 3 个 classes,我还有几个标准的 NancyFX 路由模块,用于处理各种端点等。
如果这一切都很好,正如您从 ProcessorKernel 中看到的那样,我调用了一个名为 RegisterProcessor 的方法,联系负责管理此处理器实例的 Web 应用程序并自行注册,本质上是对 Web 应用程序说,嘿,我在这里,并准备好通过 Redis 队列向我发送请求。
然而,这个 ProcessorKernel 使用 Nancy Bootstrapper 实例化为单例:
using Nancy;
using Nancy.Bootstrapper;
using Nancy.TinyIoc;
namespace processor
{
public class Bootstrapper : DefaultNancyBootstrapper
{
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
base.ApplicationStartup(container, pipelines);
container.Register<ProcessorKernel>().AsSingleton();
}
}
}
它必须是单例的原因是因为它必须接受来自 redis 的排队请求,然后处理它们并将它们发送到不同位置的各种网络服务。
有几个不同的频道,但一次只能有一个处理器收听一个频道。这最终意味着当这个 ProcessorKernel 关闭时,它必须调用 PHP 网络应用程序到 "De-Register" 本身,并通知应用程序没有任何事情为频道服务。
它找到了一个很好的挂钩点来调用这个导致我出现问题的注销。
尝试过的方法
正如您从上面的代码中看到的那样,我尝试使用 class 析构函数,虽然这不是世界上最好的解决方案,但它确实被调用了,但是 class 被拆除了在它之前returns,所以实际的注销永远不会完成,因此永远不会正确注销。
我也试过使单例 class 成为 IDisposable,并查看是否将注销代码放入 "Destroy",但根本没有被调用。
因为我正在使用 Topshelf 并且我在 ServiceApp 中有 Start/Stop 方法我知道我可以在那里调用我的例程,但我无法访问在这一点上到单例 ProcessorKernel 因为它不是 Nancy 模块所以 TinyIOC 没有解决依赖关系。
我最初也尝试在 Program.cs 中创建 ProcessorKernel 并通过 传递它ServiceApp 构造函数,虽然它创建了一个服务,但它没有注册为单例,并且无法在 Nancy Route 模块中访问,因此状态端点无法查询创建的实例以获取状态更新他们只得到了 TinyIOC 创建的单例。
如果我可以访问 Start/Stop 中的单例,那么我可以轻松地在其上放置几个 public 方法来调用 register/deregister 函数。
为了完整起见,我用来向 PHP 网络应用程序发送 post 请求的代码如下:
Utils.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text;
namespace processor
{
public class Utils
{
public static string PostData(string url, Dictionary<string, string> data)
{
string resultContent;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(Settings.AppBase);
var payload = new List<KeyValuePair<string, string>>();
foreach (var item in data)
{
payload.Add(new KeyValuePair<string, string>(item.Key, item.Value));
}
var content = new FormUrlEncodedContent(payload);
var result = client.PostAsync(url, content).Result;
resultContent = result.Content.ReadAsStringAsync().Result;
var code = result.StatusCode;
if (code == HttpStatusCode.InternalServerError)
{
Console.WriteLine(resultContent);
throw new ApplicationException("Server 500 Error while posting to: " + url);
}
if (code == HttpStatusCode.NotFound)
{
Console.WriteLine(resultContent);
throw new ApplicationException("Server 404 Error " + url + " was not a valid resource");
}
}
return resultContent;
}
}
}
总结
我只需要一种可靠的方法来调用注销代码,并确保服务通知 php-app 它即将消失,因为 php- app 我无法使用 redis 将消息发送回它,因为它根本不在听,该应用程序是 Apache 网络服务器下的标准线性 php 应用程序 运行。
顺便说一句,我愿意接受关于如何构建它的更好的想法,只要只有一个实际的 ProcessorKernel 运行 实例。注册后,它必须处理发送到其通道的每个请求,并且由于正在处理的请求可能会被多次访问并由多个不同的 Web 服务处理,因此这些请求需要保存在内存中,直到它们的处理完全完成。
在此先感谢您对此的任何想法。
更新 - 第二天:-)
所以在看到下面 Kristian 的回答,并看到 his/GrumpyDev 和 Phillip Haydon 在我的 Twitter 流上的回复后,我将我的 Singleton class 改回了 IDisposable,摆脱了 ~Finalizer , 然后更改 ServiceApp.cs 以便调用 Host.Dispose() 而不是 Host.Stop()
这随后在我的单身人士 class 上正确地调用了 Dispose 例程,这使我能够正确地关闭东西。
我也想出了另一个解决方法,我不得不承认
- a) 很丑
- b) 现已删除
然而,本着分享的精神(并且冒着尴尬的风险)我将在这里详细说明注意:我实际上不建议这样做
首先我调整了 ServiceApp.cs 现在它看起来像:
using System;
using Nancy.Hosting.Self;
using Nancy.TinyIoc;
namespace processor
{
class ServiceApp
{
private readonly NancyHost _server;
private static ProcessorKernel _kernel;
public static ProcessorKernel Kernel { get { return _kernel;}}
public ServiceApp()
{
_server = new NancyHost(new Uri(Settings.NancyUrl));
}
public void Start()
{
Console.WriteLine("Processor starting.");
_kernel = TinyIoCContainer.Current.Resolve<ProcessorKernel>();
try
{
_server.Start();
}
catch (Exception)
{
throw new ApplicationException("ERROR: Nancy Self hosting was unable to start, Aborting.");
}
_kernel.RegisterProcessor();
Console.WriteLine("Processor has started.");
}
public void Stop()
{
Console.WriteLine("Processor stopping.");
_kernel.DeRegisterProcessor();
_server.Dispose();
Console.WriteLine("Processor has stopped.");
}
}
}
如您所见,这使我能够使用 TinyIOC 在服务应用程序中获得对我的单例的引用,这使我能够在 Start/Stop 服务中保留调用 Reg/DeReg 的引用套路。
然后添加了 Public 静态内核 属性 以便我的 Nancy 模块可以调用 ServiceApp.Kernel.. . 获取他们响应 Nancy 端点查询所需的状态信息。
然而,正如我所说,此更改现已撤消,转而使用 IDisposable 做事方式。
感谢所有花时间回复的人。
在 .NET 中有一种处理 "cleanup" 的标准方法 - 它称为 IDisposable
,Nancy(及其核心组件,如 IoC 容器)广泛使用它。
只要你的对象 correctly implements IDisposable
和 Dispose
方法在顶层被调用(最好通过 using
块),你的对象应该在正确的时间被释放.
不应在 NancyHost
上调用 Stop
,而应调用 Dispose
。这将导致以下结果:
- 停止
NancyHost
(就像你已经做的那样)
- 处理引导程序
- 处理应用程序容器(在本例中为
TinyIoCContainer
)
- 处理所有实现
IDisposable
的单例实例
为了让处置一直到你的 ProcessorKernel
,你必须确保它实现 IDisposable
并将代码从你的终结器移到 Dispose
方法中.
PS!除非你正在处理 unmanaged 资源,否则你永远不应该实现终结器。这里有 some reasons。如果你 google 它,我相信你会发现更多。
我目前正在尝试找到一种调用任务的方法,该任务需要在关闭在 NancyFX 自托管应用程序内的 TinyIOC 中创建的单例对象时调用。
到目前为止,我还没有找到答案,而且我也愿意接受更好的想法来实现我即将描述的场景。
概述
我有一个正在开发的基于 PHP 的 Web 应用程序,因为这是 PHP,没有旋转 thread/server 坐着听和处理长时间 运行 任务,php 代码在请求它的浏览器请求的生命周期内存在。
有问题的应用程序需要向 Web 服务发出一些请求,这可能需要一些时间才能完成,因此我想到了使用 Topshelf 在 C# 中实现一些后端服务的想法, NancyFX 和 Stackexchange.Redis.
该服务是一个标准的 NancyFX 自托管控制台应用程序,如下所示:
Program.cs
using Topshelf;
namespace processor
{
public class Program
{
static void Main()
{
HostFactory.Run(x =>
{
x.UseLinuxIfAvailable();
x.Service<ServiceApp>(s =>
{
s.ConstructUsing(app => new ServiceApp());
s.WhenStarted(sa => sa.Start());
s.WhenStopped(sa => sa.Stop());
});
});
}
}
}
ServiceApp.cs
using System;
using Nancy.Hosting.Self;
namespace processor
{
class ServiceApp
{
private readonly NancyHost _server;
public ServiceApp()
{
_server = new NancyHost(new Uri(Settings.NancyUrl));
}
public void Start()
{
Console.WriteLine("processor starting.");
try
{
_server.Start();
}
catch (Exception)
{
throw new ApplicationException("ERROR: Nancy Self hosting was unable to start, Aborting.");
}
Console.WriteLine("processor has started.");
}
public void Stop()
{
Console.WriteLine("processor stopping.");
_server.Stop();
Console.WriteLine("processor has stopped.");
}
}
}
ProcessorKernel.cs
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using StackExchange.Redis;
namespace processor
{
public class ProcessorKernel
{
private readonly ConnectionMultiplexer _redis;
private ISubscriber _redisSubscriber;
public ProcessorKernel()
{
try
{
_redis = ConnectionMultiplexer.Connect(Settings.RedisHost);
}
catch (Exception ex)
{
throw new ApplicationException("ERROR: Could not connect to redis queue, aborting.");
}
RegisterProcessor();
RedisMonitor();
}
~ProcessorKernel()
{
var response = RequestDeregistration();
// Do some checks on the response here
}
private RegistrationResponse RequestRegistration()
{
string registrationResponse = string.Empty;
try
{
registrationResponse = Utils.PostData("/processorapi/register", new Dictionary<string, string>
{
{"channelname", Settings.ChannelName},
});
}
catch (ApplicationException ex)
{
throw new ApplicationException("ERROR: " + ex.Message + " occured while trying to register. Aborting");
}
return JsonConvert.DeserializeObject<RegistrationResponse>(registrationResponse);
}
private DeregistrationResponse RequestDeregistration()
{
var deregistrationResponse = "";
try
{
deregistrationResponse = Utils.PostData("/processorapi/deregister", new Dictionary<string, string>
{
{"channelname", Settings.ChannelName},
});
}
catch (ApplicationException ex)
{
throw new ApplicationException("ERROR: " + ex.Message + " occured while trying to deregister. Aborting");
}
return JsonConvert.DeserializeObject<DeregistrationResponse>(deregistrationResponse);
}
private void RegisterProcessor()
{
Console.WriteLine("Registering processor with php application");
RegistrationResponse response = RequestRegistration();
// Do some checks on the response here
Console.WriteLine("Processor Registered");
}
private void RedisMonitor(string channelName)
{
_redisSubscriber = _redis.GetSubscriber();
_redisSubscriber.Subscribe(channelName, (channel, message) =>
{
RedisMessageHandler(message);
});
}
private void RedisMessageHandler(string inboundMessage)
{
RedisRequest request = JsonConvert.DeserializeObject<RedisRequest>(inboundMessage);
// Do something with the message here
}
}
}
除了这 3 个 classes,我还有几个标准的 NancyFX 路由模块,用于处理各种端点等。
如果这一切都很好,正如您从 ProcessorKernel 中看到的那样,我调用了一个名为 RegisterProcessor 的方法,联系负责管理此处理器实例的 Web 应用程序并自行注册,本质上是对 Web 应用程序说,嘿,我在这里,并准备好通过 Redis 队列向我发送请求。
然而,这个 ProcessorKernel 使用 Nancy Bootstrapper 实例化为单例:
using Nancy;
using Nancy.Bootstrapper;
using Nancy.TinyIoc;
namespace processor
{
public class Bootstrapper : DefaultNancyBootstrapper
{
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
base.ApplicationStartup(container, pipelines);
container.Register<ProcessorKernel>().AsSingleton();
}
}
}
它必须是单例的原因是因为它必须接受来自 redis 的排队请求,然后处理它们并将它们发送到不同位置的各种网络服务。
有几个不同的频道,但一次只能有一个处理器收听一个频道。这最终意味着当这个 ProcessorKernel 关闭时,它必须调用 PHP 网络应用程序到 "De-Register" 本身,并通知应用程序没有任何事情为频道服务。
它找到了一个很好的挂钩点来调用这个导致我出现问题的注销。
尝试过的方法
正如您从上面的代码中看到的那样,我尝试使用 class 析构函数,虽然这不是世界上最好的解决方案,但它确实被调用了,但是 class 被拆除了在它之前returns,所以实际的注销永远不会完成,因此永远不会正确注销。
我也试过使单例 class 成为 IDisposable,并查看是否将注销代码放入 "Destroy",但根本没有被调用。
因为我正在使用 Topshelf 并且我在 ServiceApp 中有 Start/Stop 方法我知道我可以在那里调用我的例程,但我无法访问在这一点上到单例 ProcessorKernel 因为它不是 Nancy 模块所以 TinyIOC 没有解决依赖关系。
我最初也尝试在 Program.cs 中创建 ProcessorKernel 并通过 传递它ServiceApp 构造函数,虽然它创建了一个服务,但它没有注册为单例,并且无法在 Nancy Route 模块中访问,因此状态端点无法查询创建的实例以获取状态更新他们只得到了 TinyIOC 创建的单例。
如果我可以访问 Start/Stop 中的单例,那么我可以轻松地在其上放置几个 public 方法来调用 register/deregister 函数。
为了完整起见,我用来向 PHP 网络应用程序发送 post 请求的代码如下:
Utils.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text;
namespace processor
{
public class Utils
{
public static string PostData(string url, Dictionary<string, string> data)
{
string resultContent;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(Settings.AppBase);
var payload = new List<KeyValuePair<string, string>>();
foreach (var item in data)
{
payload.Add(new KeyValuePair<string, string>(item.Key, item.Value));
}
var content = new FormUrlEncodedContent(payload);
var result = client.PostAsync(url, content).Result;
resultContent = result.Content.ReadAsStringAsync().Result;
var code = result.StatusCode;
if (code == HttpStatusCode.InternalServerError)
{
Console.WriteLine(resultContent);
throw new ApplicationException("Server 500 Error while posting to: " + url);
}
if (code == HttpStatusCode.NotFound)
{
Console.WriteLine(resultContent);
throw new ApplicationException("Server 404 Error " + url + " was not a valid resource");
}
}
return resultContent;
}
}
}
总结
我只需要一种可靠的方法来调用注销代码,并确保服务通知 php-app 它即将消失,因为 php- app 我无法使用 redis 将消息发送回它,因为它根本不在听,该应用程序是 Apache 网络服务器下的标准线性 php 应用程序 运行。
顺便说一句,我愿意接受关于如何构建它的更好的想法,只要只有一个实际的 ProcessorKernel 运行 实例。注册后,它必须处理发送到其通道的每个请求,并且由于正在处理的请求可能会被多次访问并由多个不同的 Web 服务处理,因此这些请求需要保存在内存中,直到它们的处理完全完成。
在此先感谢您对此的任何想法。
更新 - 第二天:-)
所以在看到下面 Kristian 的回答,并看到 his/GrumpyDev 和 Phillip Haydon 在我的 Twitter 流上的回复后,我将我的 Singleton class 改回了 IDisposable,摆脱了 ~Finalizer , 然后更改 ServiceApp.cs 以便调用 Host.Dispose() 而不是 Host.Stop()
这随后在我的单身人士 class 上正确地调用了 Dispose 例程,这使我能够正确地关闭东西。
我也想出了另一个解决方法,我不得不承认
- a) 很丑
- b) 现已删除
然而,本着分享的精神(并且冒着尴尬的风险)我将在这里详细说明注意:我实际上不建议这样做
首先我调整了 ServiceApp.cs 现在它看起来像:
using System;
using Nancy.Hosting.Self;
using Nancy.TinyIoc;
namespace processor
{
class ServiceApp
{
private readonly NancyHost _server;
private static ProcessorKernel _kernel;
public static ProcessorKernel Kernel { get { return _kernel;}}
public ServiceApp()
{
_server = new NancyHost(new Uri(Settings.NancyUrl));
}
public void Start()
{
Console.WriteLine("Processor starting.");
_kernel = TinyIoCContainer.Current.Resolve<ProcessorKernel>();
try
{
_server.Start();
}
catch (Exception)
{
throw new ApplicationException("ERROR: Nancy Self hosting was unable to start, Aborting.");
}
_kernel.RegisterProcessor();
Console.WriteLine("Processor has started.");
}
public void Stop()
{
Console.WriteLine("Processor stopping.");
_kernel.DeRegisterProcessor();
_server.Dispose();
Console.WriteLine("Processor has stopped.");
}
}
}
如您所见,这使我能够使用 TinyIOC 在服务应用程序中获得对我的单例的引用,这使我能够在 Start/Stop 服务中保留调用 Reg/DeReg 的引用套路。
然后添加了 Public 静态内核 属性 以便我的 Nancy 模块可以调用 ServiceApp.Kernel.. . 获取他们响应 Nancy 端点查询所需的状态信息。
然而,正如我所说,此更改现已撤消,转而使用 IDisposable 做事方式。
感谢所有花时间回复的人。
在 .NET 中有一种处理 "cleanup" 的标准方法 - 它称为 IDisposable
,Nancy(及其核心组件,如 IoC 容器)广泛使用它。
只要你的对象 correctly implements IDisposable
和 Dispose
方法在顶层被调用(最好通过 using
块),你的对象应该在正确的时间被释放.
不应在 NancyHost
上调用 Stop
,而应调用 Dispose
。这将导致以下结果:
- 停止
NancyHost
(就像你已经做的那样)- 处理引导程序
- 处理应用程序容器(在本例中为
TinyIoCContainer
)- 处理所有实现
IDisposable
的单例实例
- 处理所有实现
- 处理应用程序容器(在本例中为
- 处理引导程序
为了让处置一直到你的 ProcessorKernel
,你必须确保它实现 IDisposable
并将代码从你的终结器移到 Dispose
方法中.
PS!除非你正在处理 unmanaged 资源,否则你永远不应该实现终结器。这里有 some reasons。如果你 google 它,我相信你会发现更多。