方法不是从运行Task执行的

Hak*_*tık 2 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.
    }
}
Run Code Online (Sandbox Code Playgroud)

门面课

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.
    }
}
Run Code Online (Sandbox Code Playgroud)

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}");
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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 });
    }
}
Run Code Online (Sandbox Code Playgroud)

我在前三天尝试过什么?

1-将用于调试的DLL(不是已发布的DLL,具有释放模式)部署到服务器,这不能解决问题.

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.
    }
}
Run Code Online (Sandbox Code Playgroud)

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}");
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

只是将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}");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

注意1:我注意到我在服务器上遇到问题(本地机器上根本没有出现),这是特定于本网站的应用程序池经常停止,这导致503服务在请求网站时不可用.

注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);
            }
        });
    }
Run Code Online (Sandbox Code Playgroud)

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我解决了死锁问题,但我遇到了另一个问题.当发送苹果通知时,触发此Handle方法的MVC操作被调用两次,这将导致业务规则异常(如果此操作触发两次则正常).并且根本没有达成Apple通知.
注意:当我调试在本地计算机上发送Apple Notification的代码时,一切都很好,并且通知已到达,并且Action只调用一次,之前描述的问题出现在将此代码部署到服务器之后.
注:发送谷歌通知时,这个问题不会出现在所有.

顺便说一下,这是触发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); }
    }     
}
Run Code Online (Sandbox Code Playgroud)

Ali*_*rak 9

看起来你已陷入僵局.您需要阅读有关同步上下文和ConfigureAwait的信息.

我建议你使用:

await SendersFacade.PartnerSender.SendAsync(notification);
Run Code Online (Sandbox Code Playgroud)

代替:

SendersFacade.PartnerSender.Send(notification).Wait();
Run Code Online (Sandbox Code Playgroud)

UPD:

如果无法使Send方法异步,则需要将ConfigureAwait(false)添加到等待的方法中:

await client.PostAsync("https://fcm.googleapis.com/fcm/send", content).ConfigureAwait(false);

await message.Content.ReadAsStringAsync().ConfigureAwait(false);
Run Code Online (Sandbox Code Playgroud)

这样可以避免死锁.

  • @HakamFostok就像我说的,这不是一个完整的解决方案.TBH,我个人不能在不知道你的问题的全部情况的情况下给你*完整*解决方案 - 这可能比你在这里描述的更复杂.就是这样,我能做的就是猜测.我最好的选择是:**1**:像其他人一样指出你有一个死锁情况,涉及使用`.Wait()`和`.Result`,以及**2**:你要去的*fire-and-forget*(非本机为ASP.NET设计的东西) (2认同)

Nko*_*osi 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}");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意重命名为Sendto SendAsync以正确表达意图.另外,请注意不要HttpClient在每次通话时创建新的.这可能有副作用,但它超出了这个问题和答案的范围.SO上已有很多答案可以解释这一点.

接下来确保Handler也正确地实现为异步

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
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

最后尝试确保在调用堆栈中没有阻塞调用更高,因为这会让您重新回到遇到的死锁问题.ie:调用的东西不Task IHandler<T>.Handle(T args)应该混合异步和阻塞调用.

如果无法完全理解async/await,你应该考虑阅读

Async/Await - 异步编程的最佳实践

以便更好地理解该主题.