Не получается из метода сделать Task<result>

137
14 марта 2019, 21:20

Есть метод он получает данные из базы, сохраняет их в Datatable и еще я через linq делаю отбор нужного мне количества строк. Но возникает ошибка, делать еще один метод как о не эффективно. Где я не прав.

 public Task <DataTable> GetDataFromBase(string _connectionString, string qwery,int? _stringout)
        {
           Task<DataTable>  rezulTable = null;
            string connectionString = _connectionString;
            try
            {
                using (SqlConnection connection = new SqlConnection(connectionString))
                if (connection.State != ConnectionState.Open)
                {
                    connection.Open();
                }
            }
            catch (Exception e)
            {
               MessageBox.Show("Ошибка соединения "+e);
                throw;
            }
            var dt = new DataTable();
            //получаем результат запроса в DataTable...
            using (var adapter = new System.Data.SqlClient.SqlDataAdapter(qwery, connectionString))
            {
                adapter.Fill(dt);
            }

            // выбираем количество записей
            if (_stringout!=null&&_stringout!=0)// проверяем, что строка не пустая, и не равна 0
            {
             int countRows=(int)_stringout;
             var t = dt.AsEnumerable().Take(countRows);
                // вот тут ошибка
                rezulTable = t.CopyToDataTable();
            }
            else
            {
             rezulTable=dt;
            }
            return rezulTable;
        }
Answer 1

У вас в коде определена переменная rezulTable типа Task<DataTable>. А далее вы пытаетесь присвоить ей значение типа DataTable:

var dt = new DataTable();
...
rezulTable = t.CopyToDataTable();
...
rezulTable = dt;

Чтобы ошибка исчезла можно использовать метод Task.FromResult:

rezulTable = Task.FromResult(t.CopyToDataTable());
...
rezulTable = Task.FromResult(dt);

Однако, в данном случае это не имеет большого смысла, т. к. ваш код принципиально синхронный. Нужно просто возвращать DataTable, изменив сигнатуру метода:

public DataTable GetDataFromBase(...)
{
    var resultTable = new DataTable();
    ...
    return resultTable;
}

Вероятно, у вас возникли проблемы с долгим выполнением этого метода (обращение к БД, загрузка данных), из-за чего, например, подмерзает GUI.

Что можно предпринять?

Первый вариант: запустить внутри метода отдельный поток (задачу) и вернуть её.

public Task<DataTable> GetDataFromBase(...)
{
    return Task.Run(() =>
    {
        Task<DataTable> resultTable = null;
        ...
        return resultTable;
    });
}

После чего вызывать этот метод следует с async:

await GetDataFromBase(...);

Но такой способ не рекомендуется: Task.Run Etiquette. (Хотя здесь автор статьи сам использует такой способ - как после этого верить людям?..) Оставьте метод синхронным, а Task.Run используйте там, где вызываете этот метод.

Другой вариант. Сделаем метод действительно асинхронным. Дело в том, что запуск отдельного потока/задачи следут использовать только для CPU-bound нагрузки, т. е. когда именно процессор занят длительными вычислениями. А у вас идёт IO-bound нагрузка: долго выполняются запросы к БД.

Обший шаблон метода будет выглядеть примерно так:

public async Task<DataTable> GetDataFromBaseAsync(string connectionString, string query)
{
    using (var connection = new SqlConnection(connectionString))
    {
        await connection.OpenAsync();
        using (var cmd = new SqlCommand(query, connection))
        using (var reader = await cmd.ExecuteReaderAsync().ConfigureAwait(false))
        {
            var resultTable = new DataTable();
            // здесь должен быть код создания колонок в DataTable
            // ...
            while (await reader.ReadAsync().ConfigureAwait(false))
            {
                var dataRow = resultTable.NewRow();
                for (int i = 0; i < resultTable.Columns.Count; i++)
                {
                    dataRow[i] = reader[i];
                }
                resultTable.Rows.Add(dataRow);
            }
            return resultTable;
        }
    }
}

И вот ещё что. Вы выбираете заданное количество строк уже на клиенте (с помощью параметра _stringout) - это неэффективно. Это нужно делать непосредственно в sql-запросе (используя ключевое слово TOP или LIMIT в зависимости от диалекта).

Примечание: у nullable-типов есть свойства HasValue и Value - научитесь их использовать.

Перед написанием ответа я поискал решения асинхронного заполнения дататейбла. Я разочарован. Часто предлагается использовать Task.Run. Иногда асинхронно создаётся DataReader, а далее загрузка данных выполняется простым вызовом dataTable.Load(reader) - то есть синхронно. И такие ответы приняты и заплюсованы. Моя в печали...

Нормальное решение находится по этой ссылке (её уже приводили в комментариях).

READ ALSO
Встроить в сборку pdb файл отладки

Встроить в сборку pdb файл отладки

Я не думаю что сложно, моя цель сделать этот файл "портативным", или что-то в этом смысле, можно ли как то его встроить в выходной исполняемый...

133
C# Unity - не работает скрипт у клиента

C# Unity - не работает скрипт у клиента

Пытаюсь разобраться с UNet, но что-то не до конца понимаюЯ проверяю, клиент ли игрок, и если да, то запускаю клиентскую версию функции, описанной...

143
Html Agility Pack не может спарсить сайт

Html Agility Pack не может спарсить сайт

При попытке выполнить

135
Сохранить экран как изображение или PDF

Сохранить экран как изображение или PDF

В приложении есть экраны, унаследованные от ContentPageЕсть ли какие-то способы представить эти экраны в виде изображений либо PDF-файлов или страниц?...

163