Asyn c код, кажется, работает параллельно - PullRequest
0 голосов
/ 03 апреля 2020

Заранее извиняюсь за длину этого вопроса. Я пишу приложение UWP. Из-за ограничений аппаратной платформы целью является Windows 1703 (сборка 15063). Базовая модель вызывает веб-сервис WCF для взаимодействия с SQL сервером. У меня есть метод тестирования (MSTest) для моей модели представления, который приводит к записи данных в базу данных (через веб-сервис). В том же методе испытаний я прочитал результаты обратно. Поскольку эта сборка Windows не поддерживает sql клиента, я написал другой веб-сервис, предназначенный исключительно для поддержки моих тестов, который будет запрашивать базу данных. Вкратце, проблема в том, что чтение из моей базы данных (с использованием службы поддержки) происходит до записи! Я проверил это с помощью SQL Server Profiler. Конечно, все Asyn c, но, насколько мне известно, код должен все еще выполняться в последовательности. Надеюсь, код объясняет это лучше, чем я:

    [TestMethod]
    public async Task Coolbox_Cage_ValidTest()
    {
        // Set up mocks etc...


        // Send a message to the view model. sessionViewModel will call a WFC web service to 
        // write to the DB.
        await sessionViewModel.SetInput(user, "Route-062-1");


        // If we wait for a second, the correct results are returned from the database.
        // If we don't, we get an early read where the results of SetInput have not yet been written.
        //await Task.Delay(1000);

        // GetResultsAsXDoc calls a different WCF web service to query the DB.
        XDocument xdoc = await GetResultsAsXDoc(client, "select * from STC_Box;");
        var q1 = from el in xdoc.Elements("DataSet").Elements("DataRow")
                 select el;

        // This assert will fail if we don't have the Task.Delay
        // Otherwise it succeeds.
        var b1 = from b in q1
                 where (string)b.Elements("Barcode").FirstOrDefault() == "062_S007743898_99_01"
                 && (string)b.Elements("Cage").FirstOrDefault() == "Route-062-1"
                 select b;
        Assert.IsTrue(b1.Count() == 1, "Did not find STC_Box record for 062_S007743898_99_01 in Cage 'Route-062-1'.");

    }


private async Task<XDocument> GetResultsAsXDoc(TestServices.TestServicesClient client, string sql)
{
    string xml = await client.GetDataXmlAsync(sql);
    XDocument xdoc = XDocument.Parse(xml);
    return xdoc;
}

Модель представления:

public async Task SetInput(User user, string input)
{
    // ...other stuff

    if (await loadCoolBoxIntoCage(user.UserID, rCoolbox, route)) { return; }

    // ...
}


private async Task<bool> loadCoolBoxIntoCage(string userID, CoolboxInputCode cb, RouteInputCode route)
{
    string msg = $"Is cool box {cb.ScanCode} being loaded into cage {route.ScanCode}?";
    DialogResult dialogResult = await _dialogService.GetYesNo(msg, "Confirm");
    if (dialogResult != DialogResult.Yes) return false;

    _despatchModel.LoadCoolBoxIntoCage(userID, cb.ScanCode, route.ScanCode);

    return true;
}

Модель:

        public async void LoadCoolBoxIntoCage(string userID, string CoolboxCode, string CageCode)
        {
            try
            {
                await Common.STC_LoadCoolBoxIntoCage(userID, CoolboxCode, CageCode);
            }
            catch (Exception e)
            {
                System.Diagnostics.Debug.WriteLine(e.Message);
                throw;
            }
        }


// Common (Class Library)
    public static async Task STC_LoadCoolBoxIntoCage(string username, string Coolbox, string Cage)
    {
        // Local function delegate to call the service method
        async Task del(ScannerServicesClient client)
        {
            await client.STC_LoadCoolBoxIntoCageAsync(username, Coolbox, Cage);
        }

        // Call the service, passing the method
        await CallClient(del);

        // Done
        return ;
    }

/// <summary>
/// Gets a service client, does something with it, then disposes of it.
/// Properly (?) handles WCF errors.
/// This function exists mainly so we don't have to repeat this error-handling in separate routines
/// for every ScannerServicesClient method.
/// </summary>
/// <param name="doSomethingAsync">A delegate that performs some action with the client.</param>
/// <returns>Task</returns>
/// <see cref="https://docs.microsoft.com/en-us/dotnet/framework/wcf/samples/use-close-abort-release-wcf-client-resources"/>
/// <see cref="https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-call-wcf-service-operations-asynchronously"/>
private static async Task CallClient(Func<ScannerServicesClient, Task> doSomethingAsync)
{
    ScannerServicesClient client = Services.GetScannerServicesClient();

    try
    {
        // Get the service client
        await client.OpenAsync();

        // Do something with the client
        await doSomethingAsync(client);

        // Dispose the client.
        await client.CloseAsync();
    }
    catch (FaultException<DataCommandExecutionFault> e)
    {
        client.Abort();
        throw new Exception(e.Detail.Message);
    }
    catch (CommunicationException e)
    {
        client.Abort();
        throw new Exception(e.Message);
    }
    catch (TimeoutException e)
    {
        client.Abort();
        throw new Exception(e.Message);
    }
    catch
    {
        client.Abort();
        throw;
    }
}

Основной метод веб-службы:

public void STC_LoadCoolBoxIntoCage(string username, string Coolbox, string Cage)
{
    using (var db = new Model.DbContext())
    {
        SqlParameter pReturnValue = CreateSqlParameter("RETURN_VALUE", SqlDbType.Int, null, ParameterDirection.Output); // ParameterDirection.ReturnValue does not work here!
        SqlParameter pUserID = CreateSqlParameter("UserID", SqlDbType.VarChar, username, ParameterDirection.Input);
        SqlParameter pCoolbox = CreateSqlParameter("Coolbox", SqlDbType.VarChar, Coolbox, ParameterDirection.Input);
        SqlParameter pCage = CreateSqlParameter("Cage", SqlDbType.VarChar, Cage, ParameterDirection.Input);

        var data = db.Database.SqlQuery<object>("exec @RETURN_VALUE = [dbo].[spSTC_LoadCoolBoxIntoCage2] @UserID, @Coolbox, @Cage;",
            pReturnValue, pUserID, pCoolbox, pCage);

        // Force the fetch of results (there are no results!)
        var result = data.FirstOrDefault();
    }
}

и (наконец!) Метод службы поддержки:

public string GetDataXml(string sql)
{
    string connStr = ConfigurationManager.ConnectionStrings["testDB"].ConnectionString;
    using (SqlConnection conn = new SqlConnection(connStr))
    {
        using (SqlCommand command = new SqlCommand(sql, conn))
        {
            conn.Open();

            SqlDataReader reader = command.ExecuteReader();

            return reader2Xml(reader);
        }
    }
}


private string reader2Xml(SqlDataReader reader)
{
    XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";

    DataTable schema = reader.GetSchemaTable();

    XElement root = new XElement("DataSet", new XAttribute("FieldCount", reader.FieldCount),
        new XAttribute(XNamespace.Xmlns + "xsi", "http://www.w3.org/2001/XMLSchema-instance"));


    int rowIndex = 0;

    while (reader.Read())
    {
        XElement dataRowElement = new XElement("DataRow", new XAttribute("Index", rowIndex++));

        for (int i = 0; i < reader.FieldCount; ++i)
        {
            var ia = schema.Rows[i].ItemArray;  // schema row [i] describes data column [i]

            var oName = ia[0];
            var oType = ia[12];
            object cobj = reader[i];

            // Express NULL data by omitting the element.
            if (cobj != DBNull.Value)
            {
                dataRowElement.Add(new XElement(oName.ToString(), new XAttribute("Type", oType.ToString()), cobj.ToString()));
            }
        }

        root.Add(dataRowElement);
    }

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