在NancyFX self主机单身中调用关闭任务
我目前正试图找到一种调用任务的方法,该任务需要在关闭在NancyFX自托管应用程序内的TinyIOC中创建的单例对象时调用。
到目前为止,我一直无法得出答案,而且我也愿意提供更好的实现我将要描述的场景的想法。
概观
我有一个基于PHP的Web应用程序,因为这是PHP,没有可以坐在监听和处理长时间运行任务的自旋线程/服务器,PHP代码存在于浏览器请求的使用期限内。
有问题的应用程序需要向可能需要时间完成的Web服务发出一些请求,因此我想出了在C#中实现一些后端服务的想法,使用Topshelf,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
}
}
}
除了这三个类,我还有一对标准的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的排队请求,然后处理它们并将它们发送到不同位置的各种Web服务。
有几种不同的频道,但一次只能有一个处理器正在监听一个频道。 这最终意味着,当ProcessorKernel关闭时,它必须调用PHP Web应用程序来“解除注册”本身,并通知应用程序没有任何通道正在服务。
它找到了一个很好的挂钩点,称这种取消注册导致了我的问题。
尝试的方法
从上面的代码可以看出,我尝试过使用类析构函数,虽然不是世界上最好的解决方案,但它确实被调用,但类在它返回之前被拆除,因此实际的注销从未完成,因此永远不会正确注销。
我也试过让单身人士课程成为一个IDisposable,并且看看是否将撤销登记代码放入“Destroy”内部,但是根本没有被调用。
因为我正在使用Topshelf,并且在ServiceApp中有启动/停止方法我知道我可以在那里调用我的例程,但是现在我无法访问单例ProcessorKernel ,因为它不是Nancy模块,所以TinyIOC不会不解决依赖关系。
我最初也尝试在Program.cs中创建ProcessorKernel ,并通过ServiceApp构造函数将其传入,并创建了一个服务,但没有将其注册为singleton,并且在Nancy Route模块内部不可访问,所以状态端点,因为它们只有TinyIOC创建的单例,所以无法查询创建的实例的状态更新。
如果我可以在Start / Stop中访问单例,那么我可以轻松地在其上放置一些公共方法来调用注册/注销函数。
为了完整起见,我用来将发布请求发送到PHP Web应用程序的代码如下所示:
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 Web服务器下运行的标准线性php应用程序。
只要只有一个实际的ProcessorKernel正在运行,我就可以更好地了解如何构建这个架构。 一旦它被注册,它必须处理发送到它的频道的每个请求,并且由于正在处理的请求可能被多次访问并被多个不同的Web服务处理,所以这些请求需要保存在内存中直到它们的处理完全完成。
在此先感谢您对此的任何想法。
更新 - 第二天:-)
所以在看到Kristian的回答后,我看到他的/ GrumpyDev和Phillip Haydon在我的Twitter流上的回复后,我将Singleton类更改回IDisposable,摆脱了〜Finalizer ,然后更改了ServiceApp.cs,以便它调用Host.Dispose ()而不是Host.Stop()
然后在我的单例类上正确调用Dispose例程,这使我可以正确地关闭它。
我也想出了另一个解决办法,我不得不承认
然而,本着分享的精神(以及面临尴尬的风险),我会在这里详细说明注意:我实际上并不建议这样做
首先我调整了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在服务应用程序中获取对我的单例的引用,这使我可以保持引用,以便在启动/停止服务例程中调用Reg / DeReg。
然后添加公共静态内核属性,以便我的Nancy模块可以调用ServiceApp.Kernel ...来获取他们需要的状态信息来响应Nancy端点上的查询。
然而,正如我所说,现在这种改变已经被撤销,转而采用IDisposable的做法。
感谢所有花时间回复的人。
在.NET中有一种处理“清理”的标准方式 - 它被称为IDisposable
,Nancy(以及它的核心组件,比如IoC容器)广泛使用它。
只要您的对象正确实现了IDisposable
并且在顶层调用Dispose
方法(最好通过using
块),则应该在正确的时间处理对象。
不要在NancyHost
上调用Stop
,而应该调用Dispose
。 这将导致以下结果:
NancyHost
(就像你已经做的那样) TinyIoCContainer
) IDisposable
单例实例 为了使处理一直进行到ProcessorKernel
,您必须确保它实现了IDisposable
并将代码从终结器移动到Dispose
方法中。
PS! 除非你处理非托管资源,否则绝对不应该实现终结器。 这是一些原因。 我相信你会发现更多,如果你谷歌它。
链接地址: http://www.djcxy.com/p/27479.html上一篇: Calling Shutdown Tasks in a NancyFX self host singleton