Асинхронный список задач http PostAsyn c вызывает внутреннюю ошибку сервера - PullRequest
0 голосов
/ 27 марта 2020

У меня есть приложение xamarin.forms, и там ViewModel должен сделать несколько запросов post / put к веб-API. Я думал, что сделаю асинхронно, сначала создам список задач PostAsync / PutAsyn c, а затем жду всех с Task.WhenAll.

Кажется, все работает нормально, хотя иногда (иногда очень часто) я получаю код ответа 500 внутренняя ошибка сервера. Так как это довольно общее описание ошибки, и я не могу запросить у локального сервера (не могу запустить эмулятор), трудно понять, что не так. Я думаю, что это связано с тем, что я делаю все это асинхронно, потому что у меня есть много других запросов в приложении (каждый ожидал), и я никогда не получал эту ошибку.

Вот код в моей ViewModel:

public async Task<string> Save(int handlingId, int processId)
{
        List<Task<string>> listOfTask = new List<Task<string>>();

        foreach(ProcessAction pa in CheckedItems.Where(p=>(bool)p.IsMutable))
        {
            pa.HandlingId = handlingId;
            pa.ProcessId = processId;

            if ((bool)pa.IsMutable && (bool)pa.IsChecked)
            {
                if (pa.ProcessActionId == 0)
                {
                    listOfTask.Add(pa.Add());
                }
                else
                {
                    listOfTask.Add(pa.Edit());
                }
            }    
        }

        IEnumerable<string> results = await Task.WhenAll<string>(listOfTask);

        if(results.Where(r => r != "OK").Any())
        {
            return string.Join("; ", results.Where(r => r != "OK"));
        }
        else
        {
            return "OK";
        }
}

Вот ProcessAction, который наследуется от класса Entity и имеет методы Add () и Edit (), содержащие generi c T:

public virtual async Task<string> Add()
{
        using (var client = new HttpClient())
        {
            string _Result = "OK";
            string url = Secrets.ApiAddress + $"Create{typeof(T).Name}?token=" + Secrets.TenantToken + "&UserId=" + RuntimeSettings.UserId;

            try
            {
                HttpClient httpClient = new HttpClient(new NativeMessageHandler() { Timeout = new TimeSpan(0, 0, 20), EnableUntrustedCertificates = true, DisableCaching = true });
                var serialized = JsonConvert.SerializeObject(this);
                var content = new StringContent(serialized, Encoding.UTF8, "application/json");
                HttpResponseMessage httpResponse = await Static.Functions.GetPostRetryAsync(() => httpClient.PostAsync(new Uri(url), content), TimeSpan.FromSeconds(3));
                if (!httpResponse.IsSuccessStatusCode)
                {
                    _Result = httpResponse.ReasonPhrase;
                }
                else
                {
                    var rString = await httpResponse.Content.ReadAsStringAsync();
                    AddedItem = rString;
                }
            }
            catch (Exception ex)
            {
                _Result = ex.Message;
                Static.Functions.CreateError(ex, "No connection", nameof(this.Add), this.GetType().Name);
            }

            return _Result;
        }
}

public async Task<string> Edit()
{
        string url = Secrets.ApiAddress + $"Edit{typeof(T).Name}?token=" + Secrets.TenantToken + $"&id={this.Id}&UserId={RuntimeSettings.CurrentUser.UserId}";
        string _Result = "OK";

        try
        {
            HttpClient httpClient = new HttpClient(new NativeMessageHandler() { Timeout = new TimeSpan(0, 0, 20), EnableUntrustedCertificates = true, DisableCaching = true });
            var serializedProduct = JsonConvert.SerializeObject(this);
            var content = new StringContent(serializedProduct, Encoding.UTF8, "application/json");
            HttpResponseMessage result = await Static.Functions.GetPostRetryAsync(() => httpClient.PutAsync(new Uri(url), content), TimeSpan.FromSeconds(3));
            if (!result.IsSuccessStatusCode)
            {
                _Result = result.ReasonPhrase;
            }
        }
        catch (Exception ex)
        {
            _Result = ex.Message;
            Static.Functions.CreateError(ex, "No connection", nameof(this.Edit), this.GetType().Name);
        }

        return _Result;
}

Сам метод PostAsync / PutAsyn c обернут в метод GetPostRetryAsyn c, который повторяет запрос в случае разрыва сети. Я выкладываю это, хотя я думаю, что нет проблем там:

  public static async Task<HttpResponseMessage> GetPostRetryAsync(Func<Task<HttpResponseMessage>> action, TimeSpan sleepPeriod, int tryCount = 3)
  {
        int attempted = 0;
        bool pingable = false;
        CancellationTokenSource PingCts;
        CancellationTokenSource actionCts;
        HttpResponseMessage res = new HttpResponseMessage();
        Exception exc;

        if (tryCount <= 0)
            throw new ArgumentOutOfRangeException(nameof(tryCount));

        while (true)
        {
            try
            {
                attempted++;

                if (attempted > 1)
                {
                    DependencyService.Get<IToaster>().LongAlert($"Próba {attempted}");
                }

                if (RuntimeSettings.IsVpnConnection || Secrets.ApiAddress==Secrets.WebioApiAddress)
                {
                    tryCount = 1;
                }
                else
                {
                    WiFiInfo w = await DependencyService.Get<IWifiHandler>().ConnectPreferredWifi();
                    var formattedSsid = $"\"{Static.Secrets.PreferredWifi}\"";

                    if (w.SSID == formattedSsid)
                    {
                        tryCount = 1;
                    }
                }

                PingCts = new CancellationTokenSource();
                var ping = Task.Run(() => DependencyService.Get<IWifiHandler>().PingHost(),PingCts.Token);
                actionCts = new CancellationTokenSource();
                var resTask = Task.Run(() => action(),actionCts.Token);

                Task firstFinieshed = await Task.WhenAny(ping, resTask);

                if (ping.Status == TaskStatus.RanToCompletion)
                {
                    pingable = await ping;
                    if (!pingable)
                    {
                        actionCts.Cancel();
                        exc = new ServerUnreachableException();
                        throw exc;
                    }
                    else
                    {
                        res = await resTask;
                    }
                }
                else
                {
                    PingCts.Cancel();
                    res = await resTask;
                }

                return res ; // success!
            }
            catch(Exception ex)
            {
                --tryCount;
                if (tryCount <= 0)
                {
                    throw;
                }
                await Task.Delay(sleepPeriod);
            }
        }
}

А вот методы веб-API. Они не в контроллере asyn c:

[HttpPut]
[Route("EditProcessAction")]
[ResponseType(typeof(void))]
public IHttpActionResult EditProcessAction(string token, int id, int UserId, JDE_ProcessActions item)
{
        if (token != null && token.Length > 0)
        {
            var tenants = db.JDE_Tenants.Where(t => t.TenantToken == token.Trim());
            if (tenants.Any())
            {
                var items = db.JDE_ProcessActions.AsNoTracking().Where(u => u.TenantId == tenants.FirstOrDefault().TenantId && u.ProcessActionId == id);
                if (items.Any())
                {
                    string descr = "Edycja przypisania czynności do zgłoszenia";
                    item.LmOn = DateTime.Now;
                    item.LmBy = UserId;
                    JDE_Logs Log = new JDE_Logs { UserId = UserId, Description = descr, TenantId = tenants.FirstOrDefault().TenantId, Timestamp = DateTime.Now, OldValue = new JavaScriptSerializer().Serialize(items.FirstOrDefault()), NewValue = new JavaScriptSerializer().Serialize(item) };
                    db.JDE_Logs.Add(Log);
                    db.Entry(item).State = EntityState.Modified;

                    try
                    {
                        db.SaveChanges();
                    }
                    catch (DbUpdateConcurrencyException)
                    {
                        if (!JDE_ProcessActionExists(id))
                        {
                            return NotFound();
                        }
                        else
                        {
                            throw;
                        }
                    }
                }
            }
        }

        return StatusCode(HttpStatusCode.NoContent);
}

[HttpPost]
[Route("CreateProcessAction")]
[ResponseType(typeof(JDE_ProcessActions))]
public IHttpActionResult CreateProcessAction(string token, JDE_ProcessActions item, int UserId)
{
        if (token != null && token.Length > 0)
        {
            var tenants = db.JDE_Tenants.Where(t => t.TenantToken == token.Trim());
            if (tenants.Any())
            {
                item.TenantId = tenants.FirstOrDefault().TenantId;
                item.CreatedOn = DateTime.Now;
                db.JDE_ProcessActions.Add(item);
                db.SaveChanges();
                JDE_Logs Log = new JDE_Logs { UserId = UserId, Description = "Utworzenie przypisania czynności do zgłoszenia", TenantId = tenants.FirstOrDefault().TenantId, Timestamp = DateTime.Now, NewValue = new JavaScriptSerializer().Serialize(item) };
                db.JDE_Logs.Add(Log);
                db.SaveChanges();
                return Ok(item);
            }
            else
            {
                return NotFound();
            }
        }
        else
        {
            return NotFound();
        }
}

Мне нужен способ отследить эту ошибку. Если я могу включить подробные описания ошибок, отправляемых в ответ, это может быть так. Может быть, вы уже можете сказать, что причина в том, что я запрашиваю сервер (множественные запросы изменяют SQL Server db через EF)? Если это так, как я могу сделать это правильно, не ожидая каждого запроса?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...