我正在研究ASP.NET MVC项目。
我需要一些时间来解释我的疯狂情况。
我正在尝试从MVC项目向Android和Apple设备发送推送通知。
两者的发送逻辑都是正确的,请不要浪费你的时间考虑这个
我面临的灾难是:静态类中的静态方法负责发送通知未被称为。 (我不是新鲜的程序员,我在C#编程方面有超过5年的时间)但我无法调用方法。
将您置于问题的上下文中,当我在本地计算机(开发计算机)上执行代码时,将调用并执行此方法并将通知到达设备。
当我发布MVC项目并将其部署到我们的服务器时,就不会调用静态方法。
我如何知道该方法未被调用?
因为我正在记录一个文本文件,并在第一行放了一个日志语句
调用方法之前的方法和日志语句。
调用方法之前的日志被执行并充实到文本文件,但是静态方法开头的日志没有被执行!!!!!。
这是一些代码,然后我会告诉你我试图解决的问题。
public interface IHandler<T> where T : IMessage
{
Task Handle(T args);
}
public class RequestAddedAppMonitorHandler : IHandler<RequestAdded>
{
public Task Handle(RequestAdded args)
{
return Task.Factory.StartNew(() =>
{
try
{
GoogleNotification notification = CreateAndroidPartnerAppNotification(deviceId);
// this statment is executed, and the text log file will contains this line
TracingSystem.TraceInformation("Before Send Google Notification");
SendersFacade.PartnerSender.Send(notification).Wait();
}
catch (Exception ex)
{
TracingSystem.TraceException(ex);
}
});
}
private GoogleNotification CreateAndroidPartnerAppNotification(string to)
{
return new GoogleNotification(); // some initialization and creating for the notification object.
}
}
门面课程
public static class SendersFacade
{
public static GoogleNotificationSender ClientSender { get; private set; }
public static GoogleNotificationSender PartnerSender { get; private set; }
//public static AppleNotificationSender AppleSender { get; private set; }
static SendersFacade()
{
ClientSender = new GoogleNotificationSender("correct api key");
PartnerSender = new GoogleNotificationSender("correct api key");
//AppleSender = some intialization.
}
}
Google通知发送逻辑
public class GoogleNotificationSender
{
private string _authorizationToken;
private string AuthorizationToken
{
get { return _authorizationToken; }
set
{
if (string.IsNullOrEmpty(value))
throw new InvalidOperationException("authorizationToken must not be null");
_authorizationToken = value;
}
}
public GoogleNotificationSender(string authorizationToken)
{
this.AuthorizationToken = authorizationToken;
}
public async Task Send(GoogleNotification notification)
{
// ATTENTION PLEASE
// This method is not called, and the following line is not fleshed to the log file
TracingSystem.TraceInformation("Inside Send Google notification");
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "key=" + AuthorizationToken);
string json = notification.GetJson();
StringContent content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
using (HttpResponseMessage message = await client.PostAsync("https://fcm.googleapis.com/fcm/send", content))
{
message.EnsureSuccessStatusCode();
string resultAsString = await message.Content.ReadAsStringAsync();
GoogleNotificationResult result = JsonConvert.DeserializeObject<GoogleNotificationResult>(resultAsString);
if (result.Failure > 0)
throw new Exception($"Sending Failed : {result.Results.FirstOrDefault().Error}");
}
}
}
}
Google通知类
public class GoogleNotification
{
[JsonProperty("to")]
public string To { get; set; }
[JsonProperty("data")]
public JObject Data { get; set; }
[JsonProperty("notification")]
public JObject Notification { get; set; }
// some other property which is not used at all
internal string GetJson()
{
return JsonConvert.SerializeObject(this,
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
}
}
我在前三天尝试了什么?
1-将用于调试的DLL(不是已发布的DLL,使用Release模式)部署到服务器,这不能解决问题。
2-将SendersFacade
设为不是静态类,并在其上应用单线态deisng模式,也没有工作。
public class SendersFacade
{
public static SendersFacade Instance { get; private set; }
public GoogleNotificationSender ClientSender { get; private set; }
public GoogleNotificationSender PartnerSender { get; private set; }
//public static AppleNotificationSender AppleSender { get; private set; }
static SendersFacade()
{
if (Instance != null)
Instance = new SendersFacade();
}
public SendersFacade()
{
ClientSender = new GoogleNotificationSender("correct api key");
PartnerSender = new GoogleNotificationSender("correct api key");
//AppleSender = some intialization.
}
}
3-尝试将发送的逻辑放在Handler类中,这是有效的,我能够从服务器发送通知,这是但为什么,在地狱中,这个以下代码正在工作,但以前的代码没有工作???????????
public interface IHandler<T> where T : IMessage
{
Task Handle(T args);
}
public class RequestAddedAppMonitorHandler : IHandler<RequestAdded>
{
public Task Handle(RequestAdded args)
{
return Task.Factory.StartNew(() =>
{
try
{
GoogleNotification notification = CreateAndroidPartnerAppNotification(deviceId);
// this statment is executed, and the text log file will contains this line
TracingSystem.TraceInformation("Before Send Google Notification");
SendersFacade.PartnerSender.Send(notification).Wait();
}
catch (Exception ex)
{
TracingSystem.TraceException(ex);
}
});
}
private GoogleNotification CreateAndroidPartnerAppNotification(string to)
{
return new GoogleNotification(); // some initialization and creating for the notification object.
}
private void Send(GoogleNotification notification, string authorizationToken)
{
TracingSystem.TraceInformation("Inside Send Google notification");
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "key=" + authorizationToken);
string json = notification.GetJson();
StringContent content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
using (HttpResponseMessage message = client.PostAsync("https://fcm.googleapis.com/fcm/send", content).Result)
{
message.EnsureSuccessStatusCode();
string resultAsString = message.Content.ReadAsStringAsync().Result;
GoogleNotificationResult result = JsonConvert.DeserializeObject<GoogleNotificationResult>(resultAsString);
if (result.Failure > 0)
throw new Exception($"Sending Failed : {result.Results.FirstOrDefault().Error}");
}
}
}
}
只需将send方法的逻辑添加到RequestAddedAppMonitorHandler
类就解决了问题,但我不想这样做,为什么会发生这种情况?
只是调用方法。
3-尝试制作send方法的串行方法(不使用async
),并且它也没有工作
public void Send(GoogleNotification notification)
{
TracingSystem.TraceInformation("Inside Send Google notification");
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "key=" + AuthorizationToken);
string json = notification.GetJson();
StringContent content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
using (HttpResponseMessage message = client.PostAsync(BASE_URL, content).Result)
{
message.EnsureSuccessStatusCode();
string resultAsString = message.Content.ReadAsStringAsync().Result;
GoogleNotificationResult result = JsonConvert.DeserializeObject<GoogleNotificationResult>(resultAsString);
if (result.Failure > 0)
throw new Exception($"Sending Failed : {result.Results.FirstOrDefault().Error}");
}
}
}
注1: 我注意到我在服务器上遇到问题(本地机器上根本没有出现),这是特定于本网站的应用程序池经常停止,这导致503服务在请求网站时不可用。 / p>
注2:我怀疑造成这种问题的最可能原因是线程。 但我无法达成明确的解决方案
注3:请不要认为这个问题有答案,它根本没有帮助我。
我从三天开始研究这个问题,我真的没希望,任何想法都谢谢。
<小时/> 的更新 @Nkosi的答案真的很有用,至少我现在知道是什么问题了,我决定一路同步。并避免将
async/await
与阻塞的calles混合。
所以这是我到达的结果
public class RequestAddedAppMonitorHandler : IHandler<RequestAdded>
{
public Task Handle(RequestAdded args)
{
return Task.Factory.StartNew(() =>
{
try
{
if (deviceOS.Value == DeviceOSEnum.Android.ToString())
{
GoogleNotification notification = CreateAndroidUpdateRequestMessage(args.CustomerRequest, deviceId.Value, notificationString.Title_RequestStared, message);
SendGoogleNotification(notification, "some id");
}
else if (deviceOS.Value == DeviceOSEnum.IOS.ToString())
{
AppleNotification notification = CreateAppleNotification(deviceId.Value, notificationString.Title_RequestStared, message);
AppleNotificationSender sender = new AppleNotificationSender();
sender.SendAppleNotification(notification);
}
}
catch (Exception ex)
{
TracingSystem.TraceException(ex);
}
});
}
和 AppleNotificationSender 类
public class AppleNotificationSender
{
private TcpClient client;
private string host = "gateway.push.apple.com";
private int port = 2195;
private X509Certificate2 certificate;
public AppleNotificationSender()
{
string path = HostingEnvironment.MapPath("~/Certificates.p12");
certificate = new X509Certificate2(path, "some correct password");
}
private void SetSocketKeepAliveValues(Socket socket, int KeepAliveTime, int KeepAliveInterval)
{
//KeepAliveTime: default value is 2hr
//KeepAliveInterval: default value is 1s and Detect 5 times
uint dummy = 0; //lenth = 4
byte[] inOptionValues = new byte[System.Runtime.InteropServices.Marshal.SizeOf(dummy) * 3]; //size = lenth * 3 = 12
BitConverter.GetBytes((uint)1).CopyTo(inOptionValues, 0);
BitConverter.GetBytes((uint)KeepAliveTime).CopyTo(inOptionValues, System.Runtime.InteropServices.Marshal.SizeOf(dummy));
BitConverter.GetBytes((uint)KeepAliveInterval).CopyTo(inOptionValues, System.Runtime.InteropServices.Marshal.SizeOf(dummy) * 2);
// of course there are other ways to marshal up this byte array, this is just one way
// call WSAIoctl via IOControl
// .net 3.5 type
socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
}
private bool SocketCanWrite(SslStream stream)
{
if (client == null)
return false;
if (stream == null || !stream.CanWrite)
return false;
if (!client.Client.Connected)
return false;
return client.Client.Poll(1000, SelectMode.SelectWrite);
}
private void Connect()
{
try
{
if (client == null)
client = new TcpClient();
client.Connect(host, port);
//Set keep alive on the socket may help maintain our APNS connection
try { client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); }
catch { }
// Really not sure if this will work on MONO....
// This may help windows azure users
try
{
SetSocketKeepAliveValues(client.Client, (int)TimeSpan.FromMinutes(20).TotalMilliseconds, (int)TimeSpan.FromSeconds(30).TotalMilliseconds);
}
catch { }
}
catch (Exception ex)
{
throw new Exception("Failed to Connect, check your firewall settings!", ex);
}
}
public void SendAppleNotification(AppleNotification notification)
{
SslStream stream = null;
try
{
Connect();
stream = new SslStream(client.GetStream(),
false,
(sender, cert, chain, policyErrors) => true,
(sender, targetHost, localCerts, remoteCert, acceptableIssuers) => certificate);
try
{
X509CertificateCollection collection = new X509CertificateCollection();
collection.Add(certificate);
stream.AuthenticateAsClient(host, collection, System.Security.Authentication.SslProtocols.Tls, false);
}
catch (System.Security.Authentication.AuthenticationException ex)
{
throw new Exception("SSL Stream Failed to Authenticate as Client", ex);
}
if (!stream.IsMutuallyAuthenticated)
throw new Exception("SSL Stream Failed to Authenticate", null);
if (!stream.CanWrite)
throw new Exception("SSL Stream is not Writable", null);
if (!SocketCanWrite(stream))
Connect();
byte[] data = notification.ToBytes();
stream.Write(data, 0, data.Length);
//TracingSystem.TraceInformation("Write to stream ended.");
}
catch (Exception)
{
TracingSystem.TraceError("Error in sending Apple notification");
throw;
}
finally
{
try { stream?.Close(); } catch { }
try { stream?.Dispose(); } catch { }
try { client?.Client?.Shutdown(SocketShutdown.Both); } catch { }
try { client?.Client?.Dispose(); } catch { }
try { client?.Close(); } catch { }
client = null;
}
}
}
现在我解决了死锁问题,但是我遇到了另一个问题。当发送苹果通知时,触发此Handle
方法的MVC操作被调用两次,这将导致业务规则异常(如果此操作触发两次则正常)。并且根本没有达成Apple通知。
注意:当我调试在本地机器上发送Apple Notification的代码时,一切都很好,并且通知已到达,并且Action只调用一次,之前描述的问题就出现在部署之后代码到服务器。
注意:发送Google通知 时,不会出现此问题。
顺便说一下,触发Handle方法
public class MessageBus : ICommandSender
{
public static MessageBus Instance { get; private set; }
private MessageBus()
{
handlers = new List<Delegate>();
}
static MessageBus()
{
if (Instance == null)
Instance = new MessageBus();
}
private List<Delegate> handlers;
public void Send<T>(T command) where T : ICommand
{
List<Task> tasks = new List<Task>();
foreach (Func<T, Task> handle in handlers.OfType<Func<T, Task>>())
{
try { tasks.Add(handle(command)); }
catch (Exception ex) { TracingSystem.TraceException(ex); }
}
try { Task.WaitAll(tasks.ToArray()); }
catch (BusinessRuleException buzEx) { TracingSystem.TraceException(buzEx); throw buzEx; }
catch (Exception ex) { TracingSystem.TraceException(ex); }
}
}
答案 0 :(得分:9)
看起来你已陷入僵局。您需要阅读有关同步上下文和ConfigureAwait的信息。
我建议您使用:
await SendersFacade.PartnerSender.SendAsync(notification);
而不是:
SendersFacade.PartnerSender.Send(notification).Wait();
UPD:
如果无法使Send方法异步,则需要将ConfigureAwait(false)添加到等待的方法中:
await client.PostAsync("https://fcm.googleapis.com/fcm/send", content).ConfigureAwait(false);
await message.Content.ReadAsStringAsync().ConfigureAwait(false);
这可以避免死锁。
答案 1 :(得分:5)
但是为什么这个以下代码正在运行,但以前的代码是不工作的?
工作代码有效,因为它全部被同步调用,并没有混合async / await和阻塞调用。
在上一段代码中,您将async/await
与.Result
或.Wait()
等阻止调用混合在一起,这可能会导致死锁。你要么一直异步,要么一直同步。
我建议你重构GoogleNotificationSender
,确保它一直是异步
public class GoogleNotificationSender {
private HttpClient client;
private string authorizationToken;
public GoogleNotificationSender(string authorizationToken) {
this.AuthorizationToken = authorizationToken;
}
private string AuthorizationToken {
get { return authorizationToken; }
set {
if (string.IsNullOrEmpty(value))
throw new InvalidOperationException("authorizationToken must not be null");
authorizationToken = value;
}
}
private HttpClient Client {
get {
if (client == null) {
client = new HttpClient();
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "key=" + AuthorizationToken);
}
return client;
}
}
public async Task SendAsync(GoogleNotification notification) {
TracingSystem.TraceInformation("Inside Send Google notification");
var json = notification.GetJson();
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
var requestUri = "https://fcm.googleapis.com/fcm/send";
using (var message = await Client.PostAsync(requestUri, content)) {
message.EnsureSuccessStatusCode();
var result = await message.Content.ReadAsAsync<GoogleNotificationResult>();
if (result.Failure > 0)
throw new Exception($"Sending Failed : {result.Results.FirstOrDefault().Error}");
}
}
}
请注意,将Send
重命名为SendAsync
以正确表达意图。另外,请注意不要在每次通话时创建新的HttpClient
。这可能有副作用,但它超出了这个问题和答案的范围。 SO上已有很多答案可以解释这一点。
接下来确保Handler也正确实现为async
public class RequestAddedAppMonitorHandler : IHandler<RequestAdded> {
public async Task Handle(RequestAdded args) {
try {
string deviceId = args.DeviceId;//This is an assumption here
var notification = CreateAndroidPartnerAppNotification(deviceId);
// this statment is executed, and the text log file will contains this line
TracingSystem.TraceInformation("Before Send Google Notification");
await SendersFacade.PartnerSender.SendAsync(notification);
} catch (Exception ex) {
TracingSystem.TraceException(ex);
}
}
private GoogleNotification CreateAndroidPartnerAppNotification(string to) {
// some initialization and creating for the notification object.
return new GoogleNotification() {
To = to
};
}
}
最后尝试确保在调用堆栈中没有阻塞调用更高,因为这会让您重新回到遇到的死锁问题。即:调用Task IHandler<T>.Handle(T args)
的内容不应该混合异步和阻塞调用。
如果无法完全理解async / await,你应该考虑阅读
Async/Await - Best Practices in Asynchronous Programming
以更好地了解该主题。
答案 2 :(得分:0)
我个人建议给PushSharp一个机会。它提供了一个很棒的代理异步解决方案,用于将通知推送到iOS,Android,Chrome和Windows Phone。
发现使用自己更容易,并报告失败的尝试推送。所有开源来自https://github.com/Redth/PushSharp或通过NuGet
答案 3 :(得分:0)
我认为这个问题是你在这里打电话给async Task
的回复类型:
public async Task Send(GoogleNotification notification)
...但您实际上从未使用Task.Run
启动该任务。你可以在这里打电话给.Wait()
:
SendersFacade.PartnerSender.Send(notification).Wait();
...但这不是它的工作原理, 你实际上必须开始任务才能等待它 ,它只会在那里永远等待那样
如果您将Send
方法更改为具有此类签名和正文,则可以正常工作:
public Task Send(GoogleNotification notification)
{
return Task.Run(()=>
{
TracingSystem.TraceInformation("Inside Send Google notification");
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "key=" + AuthorizationToken);
string json = notification.GetJson();
StringContent content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
using (HttpResponseMessage message =client.PostAsync("https://fcm.googleapis.com/fcm/send", content).Result)
{
message.EnsureSuccessStatusCode();
string resultAsString = message.Content.ReadAsStringAsync().Result;
GoogleNotificationResult result = JsonConvert.DeserializeObject<GoogleNotificationResult>(resultAsString);
if (result.Failure > 0)
throw new Exception($"Sending Failed : {result.Results.FirstOrDefault().Error}");
}
}
});
}
请注意,我还删除了方法体内的await
个关键字 - 我想保持简单并在父任务中同步运行,毕竟我们总体上是异步的,所以它不是在身体内部发挥作用。
这是一个完整的示例,将其重新打入控制台应用程序并开始使用...
static void Main(string[] args)
{
Console.WriteLine("Press any key to run.");
Example thing = new Example();
while (Console.ReadKey() != null)
thing.Send();
}
class Example
{
Task<DisposableThing> DoTask()
{
return Task.Run(() => { Console.WriteLine("DoTask()"); return new DisposableThing(); });
}
Task<DisposableThing> DoTaskWillNotWork()
{
return new Task<DisposableThing>(() => { Console.WriteLine("DoTaskWillNotWork()"); return new DisposableThing(); });
}
async Task<DisposableThing> DoAsync()
{
Func<DisposableThing> action = new Func<DisposableThing>(() =>
{
Console.WriteLine("DoAsync()");
return new DisposableThing();
});
return await Task.Run(action);
}
public Task Send()
{
return Task.Run(() =>
{
using (DisposableThing client = new DisposableThing())
{
using (DisposableThing message = DoAsync().Result)
{
DisposableThing resultAsString = DoTask().Result;
DisposableThing resultAsString2 = DoTaskWillNotWork().Result;
}
}
});
}
}
class DisposableThing : IDisposable
{
public void Dispose()
{
//not much to do
}
}
希望这有帮助!
答案 4 :(得分:0)
我觉得死锁是罪魁祸首,检查https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
行HttpResponseMessage message = client.PostAsync("https://fcm.googleapis.com/fcm/send", content).Result
async和await的组合与调用.Wait()方法和.Result属性而不是等待异步任务方法中的任务一直都会导致死锁,如此处所述。 https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
顺便说一下,我认为您对Task.Factory.StartNew
的使用是正确的。是否有任何特殊原因试图操纵线程(或任务)而不是将其留在框架上。以下代码有问题吗?
public class RequestAddedAppMonitorHandler : IHandler<RequestAdded>
{
public async Task Handle(RequestAdded args)
{
try
{
GoogleNotification notification = CreateAndroidPartnerAppNotification(deviceId);
// this statment is executed, and the text log file will contains this line
TracingSystem.TraceInformation("Before Send Google Notification");
await SendersFacade.PartnerSender.Send(notification);
}
catch (Exception ex)
{
TracingSystem.TraceException(ex);
}
}
}