不运行任务

时间:2017-07-11 10:30:15

标签: c# asp.net-mvc

我正在研究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); }
    }     
}

5 个答案:

答案 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);
        }
    }
}