Проблема безопасности потоков во время Async, как обойти это

Мне нужно выполнить 4 запроса HttpClient из разных методов, используя один и тот же экземпляр HttpClient, что, как мне кажется, вызывает проблему безопасности потоков. Позвольте мне объяснить это подробнее.

Внутри приложения для Windows Phone, которое я разрабатываю, есть класс MainViewModel. Этот класс имеет асинхронный метод для получения данных с веб-сервера и обработки ответа. Метод Async называется «LoadData». У меня даже есть еще 2 асинхронных метода («ScheduleArrayAsync» и «CurrActivityAsync») в этом классе, которые помогают обрабатывать данные, которые я получаю с сервера.

Из метода «LoadData» я делаю 3 запроса HttpClient (эта часть работает как шарм), при обработке ответа от всех этих трех запросов мне нужно вызвать метод «ScheduleArrayAsync». Оттуда я должен сделать новый запрос HttpClient (вот в чем проблема). Этот последний запрос никогда не будет сгенерирован, и код ошибки не будет сгенерирован, даже когда я использую оператор try/catch.

Что заставляет меня думать, что это проблема с потоками, так это то, что если я передам последний запрос HttpClient методу «LoadData», просто в качестве теста, он снова заработает.

Класс MainViewModel: -

public class MainViewModel : INotifyPropertyChanged
{
    public MainViewModel()
    {
        this.Employees = new ObservableCollection<Employee>();
    }

    public ObservableCollection<Employee> Employees { get; private set; }

    private JsonTextWriter jsonW;

    private string Owner;

    private RequestResponse reqPList;

    public bool IsDataLoaded
    {
        get;
        private set;
    }
    public async void LoadData()
    {
        var baseUri = new Uri("https://uri/");

        await CookieHandler.GetCookies(baseUri); // A separate request to get some cookies

        reqPList = new RequestResponse();  // The class that handle the Httpclinet

        await reqPList.GetResponse(baseUri, pList); // First request
        XmlConvertor.ConvertToXml(reqPList.Response);
        var phoneListResponse = XmlConvertor.XmlString;

        await reqPList.GetResponse(baseUri, currActiv); // Second request
        XmlConvertor.ConvertToXml(reqPList.Response);
        var currActivResponse = XmlConvertor.XmlString;

        await reqPList.GetResponse(baseUri, sched);  // Third request
        XmlConvertor.ConvertToXml(reqPList.Response);
        var schedResponse = XmlConvertor.XmlString;

        //await reqPList.GetSlotInforPOST("154215");
        var handler = new DataHandler();
        await handler.phoneListHandler(phoneListResponse);
        await handler.CurrActivitiesHandler(currActivResponse);
        await handler.ScheduleHandler(schedResponse);
        /// Do some processing included call this line 

                    #region Current activity
                    CurrActivityAsync(item, handler.currActivitiesJSON);
                    #endregion


        this.IsDataLoaded = true;
    }

    private async void CurrActivityAsync(JToken token, string jString)
    {
        // Some processing
    }

    private async void ScheduleArrayAsync(JToken token, string jString)
    {
        try
        {
            // Do some more processing and call the fourth request 
                            if (addedInfo[0].Contains("slotInfo"))
                                await reqPList.GetSlotInforPOST(addedInfo[1]);
                            else if (addedInfo[0].Contains("vacationInfo"))
                                await reqPList.GetVacationSlotInfoPOST(addedInfo[1], addedInfo[2]);

        }
        catch (Exception exp)
        {

            var d = exp.Message;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged(String propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (null != handler)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Ниже приведен класс RequestResponse:

public class RequestResponse
{
    public string Response { get; private set; }

    private HttpClient client = new HttpClient(new HttpClientHandler()
    {
        UseCookies = true,
        CookieContainer = CookieHandler.Cookiejar,
        AllowAutoRedirect = false,
        AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
    });

    public async Task<string> GetResponse(Uri baseuri, string uriString)
    {
        if (client.BaseAddress == null)
        {
            client.BaseAddress = baseuri; 
        }
        client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/xhtml+xml"));
        var response = await client.GetAsync(baseuri + uriString);
        string webresponse = null;
        if (response.IsSuccessStatusCode)
        {
            var resp = await response.Content.ReadAsByteArrayAsync();
            var encode = Encoding.GetEncoding("iso-8859-1");
            var respString = encode.GetString(resp, 0, resp.Length - 1);
            webresponse = respString;
        }
        return Response = webresponse;
    }

    public async Task<string> GetSlotInforPOST(string timeId)
    {
        /// If the method is called from inside 'LoadData' it works.
        /// But if it is called from inside ScheduleArrayAsync, it will break at line marked with //***
        try
        {
            var baseUri = new Uri("https://uri/");
            const string slotInfo = "cgi-bin/slotInfo.pl";

            if (client.BaseAddress == null)
                client.BaseAddress = baseUri;
            client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
            HttpContent content = new FormUrlEncodedContent(new[]
            {
                new KeyValuePair<string,string>("timeId",timeId)
            });
            var response = await client.GetAsync(baseUri + slotInfo);  // ***
            string webresponse;
            if (response.IsSuccessStatusCode)
            {
                var respo = await client.PostAsync(baseUri + slotInfo, content);
                var resp = await respo.Content.ReadAsByteArrayAsync();
                var encode = Encoding.GetEncoding("iso-8859-1");
                var respString = encode.GetString(resp, 0, resp.Length - 1);
                webresponse = respString;

            }
        }
        catch (Exception exp)
        {
            var s = exp.Message;
        }
        return Response;
    }

    public async Task<string> GetVacationSlotInfoPOST(string vacationId, string date)
    {
        // Do some HttpClient Request
    }
}

Я правильно понимаю задачу? Как это преодолеть? так как мне действительно нужно сделать последний запрос HttpClient из «ScheduleArrayAsync», а не из «LoadData»

Изменить Просто хотел упомянуть, что во время моих усилий по решению проблемы у меня был другой экземпляр HttpClient в каждом методе внутри класса RequestResponse. Помимо того, как я упоминал выше, когда я вызываю четвертый запрос из метода «LoadData», все работает так, как задумано, даже если это один экземпляр httpclient.


person DreamNet    schedule 30.03.2014    source источник
comment
using the same HttpClient instance. А ты это зачем? Вы как-то к нему привязаны? Вы можете создать новый и не иметь ни одной из этих проблем.   -  person nvoigt    schedule 30.03.2014
comment
@nvoigt - почти в точности то, что я собирался опубликовать :)   -  person Martin James    schedule 30.03.2014
comment
@nvoigt, разве это не лучше с точки зрения производительности? Кажется, я кое-что читал об этом здесь: - -httpclient-for-rest-calls-maximum-throughput?forum=netfxnetcom" rel="nofollow noreferrer">social.msdn.microsoft.com/Forums/en-US/   -  person DreamNet    schedule 30.03.2014
comment
Во всяком случае, попробую это, когда вернусь домой, если ничего нового не появится.   -  person DreamNet    schedule 30.03.2014
comment
@DreamNet действительно так. Но правильный результат превосходит производительность почти во всех случаях :)   -  person nvoigt    schedule 30.03.2014
comment
@nvoigt, потому что HttpClient был разработан для повторного использования, и это дает значительное количество преимуществ. Нет проблем с повторным использованием HttpClient. Однако в этом классе происходит множество странных вещей, которые могут вызвать проблемы с потоками.   -  person Darrel Miller    schedule 30.03.2014
comment
@DarrelMiller, не могли бы вы подробнее объяснить, что вы имеете в виду в своем последнем заявлении? Может мне поможет.   -  person DreamNet    schedule 30.03.2014
comment
@nvoigt, пожалуйста, посмотрите на обновленную информацию   -  person DreamNet    schedule 30.03.2014
comment
Внезапно переместите код, устанавливающий BaseAddress, в конструктор. Также то же самое для DefaultHeaders. Если вам нужно изменить принятие для каждого запроса, создайте экземпляр HttpRequestMessage, измените его и используйте SendAsync. ConvertToXml устанавливает статическое значение, измените его, чтобы вместо этого возвращалось XML. Вы не проверяете код состояния из своего метода PostAsync.   -  person Darrel Miller    schedule 30.03.2014
comment
@DarrelMiller Спасибо за советы, сейчас попробую. Как вы думаете, это может быть причиной?   -  person DreamNet    schedule 30.03.2014
comment
@DarrelMiller Я действительно не понимаю, почему при перемещении await reqPList.GetSlotInforPOST(addedInfo[1]); от метода «ScheduleArrayAsync» к методу «LoadData», все работает, но нет, если я оставлю его   -  person DreamNet    schedule 30.03.2014
comment
@DreamNet Возможно, у вас есть асинхронная пустота в качестве подписи. Это проглотит любые возникающие исключения. Измените его на асинхронную задачу.   -  person Darrel Miller    schedule 30.03.2014
comment
@DarrelMiller, пожалуйста, проверьте ответ, который я публикую, особенно. последняя строчка ;) Спасибо бро.   -  person DreamNet    schedule 30.03.2014


Ответы (2)


Возможно, это асинхронная пустота, которую вы используете в качестве подписи в некоторых методах. Использование async void предотвратит перехват исключений вызывающими функциями. Исключения, создаваемые асинхронными методами void, могут быть перехвачены только глобальными обработчиками исключений.

person Darrel Miller    schedule 30.03.2014
comment
Хотя я согласен с тем, что использование async void было источником проблемы, это не потому, что он проглатывает исключения. Наоборот; async void будет очень громко отображать исключения. - person Stephen Cleary; 31.03.2014
comment
@StephenCleary Моя версия звучит лучше? - person Darrel Miller; 31.03.2014

Наконец, я исправил это, благодаря Даррелу.

Чтобы решить эту проблему, мне нужно было изменить подпись метода CurrActivityAsync.

от

private async void CurrActivityAsync(JToken token, string jString)

to

private void CurrActivityAsync(JToken token, string jString)

Поскольку в этом методе не было ожидания, поэтому ему не нужна была асинхронность в подписи. Плохо, что я не упомянул об этом в своем первоначальном посте.

Это было единственное, что мне нужно было сделать, но я даже изменил подпись моего «ScheduleArrayAsync», чтобы она была более правильной для Async.

от

private async void ScheduleArrayAsync(JToken token, string jString)

to

private async Task ScheduleArrayAsync(JToken token, string jString)

Даррел, пожалуйста, напишите свой последний комментарий в качестве ответа, чтобы получить кредиты.

person DreamNet    schedule 30.03.2014