Страницы

Поиск по вопросам

пятница, 7 декабря 2018 г.

Вызов конструктора класса родителя в Java. В чём важность?

Доброго времени суток всем. Читая книгу Брюса Эккеля, натолкнулся на следующий текст:
Конечно, очень важно, чтобы подобъект базового класса был правильно инициализирован, и гарантировать это можно только одним способом: выполнить инициализацию в конструкторе, вызывая при этом конструктор базового класса, у которого есть необходимые знания и привилегии для проведения инициализации базового класса. Java автоматически вставляет вызовы конструктора базового класса в конструктор производного класса.
Помогите разобраться, почему же так важно вызвать конструктор базового класса для класса наследника? И что будет если это не произойдёт (теоретически)? Спасибо.


Ответ

Базовый класс по сути является как-бы частью наследника. Поэтому, если он не будет проинициализирован, то работать ничего не будет.
Пример:
public class A { protected int valA = 1; }
public class B extends A { protected int valB = 2; public int getVal(){ return valB + valA; } }
Наследование позволяет писать такую конструкцию. Если родительский класс не будет инициализировн, то в строке return valB + valA; будет неизвестно значение valA.
Насколько я понимаю, не может быть ситуации, когда конструктор базового класса не вызовется (поправьте меня, если я ошибаюсь). Пример автоматического вызова конструкторов базовых классов

Зачем нужен метод Arrays.asList()?

Зачем нужен метод Arrays.asList() ? В чем его суть?


Ответ

Как и следует из документации: формирует список на основе массива. Массив при этом используется для внутреннего представления списка. Таким образом сохраняется связь между списком и исходным массивом:
изменения в массиве отразятся на списке:
String[] a = { "foo", "bar", "baz"}; List list = Arrays.asList(a); System.out.println(list); // [foo, bar, baz]
a[0] = "aaa"; System.out.println(list); // [aaa, bar, baz] изменения в списке отразятся на массиве:
String[] a = { "foo", "bar", "baz"}; List list = Arrays.asList(a); System.out.println(list); // [foo, bar, baz]
list.set(0, "bbb"); System.out.println(Arrays.toString(a)); // [bbb, bar, baz]
Если массив содержит объекты, очевидно, и массив и список будут ссылаться на одни и те же экземпляры:
Object[] a = { new Object(), new Object(), new Object()}; List list = Arrays.asList(a); System.out.println(a[0] == list.get(0)); // true

Как уже упомянул @Oleg Chiruhin, тот факт, что метод принимает в качестве параметра массив в форме аргументов переменной длины, позволяет использовать его для относительно удобной инициализации списков:
List list = Arrays.asList(new Foo(...), new Foo(...), new Foo(...));
сравните с
List list1 = new ArrayList<>(); list.add(new Foo(...)); list.add(new Foo(...)); list.add(new Foo(...));

Как пользоваться делегатами?


Я новичёк в этом деле, пытаюсь изучить и научиться пользоваться делегатами. Подскажите, как исправить?


Ответ

Смотрите.
Делегат — это просто такой тип, что в переменные этого типа вы можете записывать не числа и объекты, а функции*, и вызывать их потом. Вот собственно и всё, что вам нужно знать для начала.
Чтобы объявить такую переменную, компилятор должен знать какого типа аргументы будут у этой функции, и какого типа возвращаемое значение. Это ему нужно, чтобы он знал, как правильно вызывать функцию по этой переменной, и какого типа будет результат. Когда вы объявляете делегат, вы именно эту информацию и даёте:
delegate int mydel(int x, int y); ^ \_____________________/ | | объявляется делегат | | а это показывает, какие функции использовать с этим делегатом только вместо имени функции название делегата
У вас в коде функция возвращает значение, поэтому в определении делегата тоже должно быть возвращаемое значение.
Вы можете исправить код так:
class Program { delegate int mydel(int x, int y); // поменяли возвращаемое значение на int
static int sum(int x, int y) { return x + y; }
static void Main(string[] args) { mydel g = sum; // здесь можно без new, просто присвоить. так проще, правда? int result = g(10, 5); Console.WriteLine("Sum = " + result); } }

*(Примечание для знатоков.) Да, это упрощение. На самом деле у нас multicast delegate, так что их можно складывать и вызывать через Invoke/BeginInvoke. И да, делегат является объектом. Я упрощаю картину для понятности.

Случаи указания типа данных в условии цикла

Добрый вечер. Пришел с вопросом. Читал тут книгу, в одном фрагменте было написано, что если я укажу тип данных у переменной в условии функции, то значение переменной будет иметь большую область видимости, чем если бы я указал переменную без типа. То есть без типа переменная будет действовать, пока активен цикл, но с типом данных она будет действовать и на еще одни скобки.
Решил проверить. Написал с int'ом-все сработало. Убрал int-и программа стала использовать значение переменной в цикле во всей программе и по какому-то принципу компилятор его даже изменил, по какому-я не понимаю.
Вопрос: Я некорректно понял инфу из учебника? Да, то как все должно быть на самом деле?
#include using namespace std;
int main() { int done=5; int howmany=3;
for( done=1; done cout<Вывод:
7 7 3


Ответ

Рассмотрим следующую программу
#include
int main() { int done = 5; int howmany = 3;
for ( done = 1; done < howmany; done++ ) { std::cout << done << std::endl; }
std::cout << done << std::endl;
return 0; }
Ее вывод на консоль
1 2 3
В этой программе в блоке кода функции main объявлена переменная done. Сначала эта переменная инициализируется значением 5
int done = 5;
а затем в предложении for ей присваивается значение 1
for ( done = 1; done < howmany; done++ ) ^^^^^^^^
После выхода из цикла эта переменная будет иметь значение 3.
Теперь рассмотрим следующую программу
#include
int main() { int done = 5; int howmany = 3;
for ( int done = 1; done < howmany; done++ ) { std::cout << done << std::endl; }
std::cout << done << std::endl;
return 0; }
Ее вывод на консоль
1 2 5
В этой программе в предложении for объявляется переменная с именем done, совпадающем с именем переменной, объявленной в блоке кода main.
for ( int done = 1; done < howmany; done++ ) ^^^^^^^^^^^^
Эта переменная в предложении for скрывает одноименную переменную с таким же именем, объявленную во внешнем блоке кода. Ее область определения - это тело цикла for. После выхода из цикла данная переменная прекращает свое существование.
Из стандарта C++ (6.5.3 The for statement)
3 If the for-init-statement is a declaration, the scope of the name(s) declared extends to the end of the for statement.
Поэтому последняя строка вывода программы
5
уже выводит на консоль значение переменной done, которая была объявлена до цикла в блоке кода функции main
В общем случае имя, объявленное во внутренней области объявления скрывает такое же имя, объявленное во внешней области объявления
Из стандарта C++ (3.3.10 Name hiding)
1 A name can be hidden by an explicit declaration of that same name in a nested declarative region or derived class (10.2).
Обратите внимание, что есть важное отличие в определении предложения for между C++ и C.
В C++ во второй части предложения for, где записывается условие, также может быть объявление.
Рассмотрите следующую демонстрационную программу
#include
int main() { int done = 5; int howmany = 3;
for ( ; int howmany = done; done-- ) { std::cout << done << std::endl; }
std::cout << '
' << howmany << std::endl;
return 0; }
Ее вывод на консоль
5 4 3 2 1
3

При каждой итерации данного цикла for
for ( ; int howmany = done; done-- ) ^^^^^^^^^^^^^^^^^^
объявляется переменная howmany, которая скрывает переменную с таким же именем, объявленную в функции main. Значение, присвоенное этой переменной, преобразуется к булевскому типу. Если оно равно 0, то условие будет ложным и произойдет выход из цикла.
После цикла выводится уже значение переменной howmany, объявленной в main
Ну, и напоследок пример, когда в предложении for сразу в двух его частях объявляются переменные, которые скрывают одноименные переменные с теми же именами, объявленные в main. После выхода из цикла эти переменные прекращают свое существование, и становятся видимыми переменные, объявленные в main
#include
int main() { int done = 10; int howmany = 20;
for ( int done = 5; int howmany = done; done-- ) { std::cout << done << std::endl; }
std::cout << '
' << done << std::endl; std::cout << howmany << std::endl;
return 0; }
Вывод этой программы на консоль
5 4 3 2 1
10 20

Предложение for в этой программе логически может быть представлено как
{ int done = 5;
Label_repeat: { int howmany = done;
if ( howmany != 0 ) { std::cout << done << std::endl; done--; goto Label_repeat; } else { goto Label_exit; } } } Label_exit: //...

WPF Таблица XAML

Ребят подскажите мне тугому как сделать подобную выгрузку элементов с бд с постраничной или динамической прокруткой данных? Задача: В базе хранятся фотографии и фио студентов. Требуется выводить фотографию и под ней ФИО студента.

Прошу прощения за столь глупый вопрос но для меня это пока очень сложно. Возможно кто подскажет готовый пример. Это было бы проще для меня
На данный момент инф. берется таким способом.


Ответ

Окей, ну давайте напишем как это нужно делать. Попробуем сразу сделать из этого MVVM.
Начнём с модели. Главное — модель должна стараться абстрагировать от нас то, что у неё под капотом. Имея это в виду, организуем её просто как список модельных объектов в памяти. Внешний код не должен от этого зависеть.
Итак:
public class StudentModel { public string Name { get; set; } public Uri ImageUri { get; set; } }
public class StudentListModel { const string uri = "https://www.gravatar.com/avatar/f81585f940e68c3bab934cc6f59c901e?" + "s=328&d=identicon&r=PG";
public IEnumerable GetAllStudents() => Enumerable.Range(0, 200).Select(n => new StudentModel() { Name = $"Student #{n}", ImageUri = new Uri(uri) }); }
Функциональность «получить n-ую страницу» я оставляю на VM, держа в голове, что в реальности у вас вместо IEnumerable будет IQueryable. Если вам хочется сделать репозиторий поверх вашего обращения с базой (например потому, что вы не пользуетесь EF), почему бы и нет.
С моделью покамест всё, впиливать базу сюда уж придётся вам лично. Перейдём к VM.
Для начала, базовый класс для VM. Если вы пользуетесь каким-нибудь MVVM-фреймворком, он у вас уже есть. Если нет, напишем его. Он должен имплементировать INotifyPropertyChanged
class VM : INotifyPropertyChanged { protected bool Set(ref T field, T value, [CallerMemberName] string propertyName = null) { if (EqualityComparer.Default.Equals(field, value)) return false;
field = value; RaisePropertyChanged(propertyName); return true; }
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public event PropertyChangedEventHandler PropertyChanged; }
Теперь класс, отвечающий за одного студента. С этим всё просто:
class StudentVM : VM { string name; public string Name { get { return name; } set { Set(ref name, value); } }
Uri imageUri; public Uri ImageUri { get { return imageUri; } set { Set(ref imageUri, value); } }
public StudentVM(string name, Uri uri) { this.name = name; this.imageUri = uri; } }
Если вы пишете в соответствии с трендами, и у вас иммутабельные классы, получится проще:
class StudentVM : VM { public string Name { get; } public Uri ImageUri { get; }
public StudentVM(string name, Uri uri) { this.Name = name; this.ImageUri = uri; } }
Дальше — класс, занимающийся списком студентов. Будем писать его по кускам. Начнём.
class StudentListVM : VM {
Нам нужно свойство, отвечающее за количество страниц. Без него никак. Пишем:
int totalPages; public int TotalPages { get { return totalPages; } private set { if (value < 0) throw new ArgumentException(nameof(TotalPages)); Set(ref totalPages, value); } }
Пока всё просто. Да, наверняка мы захотим ещё показать в UI что-то особое, если у нас нет ни одного студента, поэтому положим вспомогательное свойство:
bool havePages; public bool HavePages { get { return havePages; } private set { Set(ref havePages, value); } }
и не забудем установить его в сеттере TotalPages
private set { if (value < 0) throw new ArgumentException(nameof(TotalPages)); Set(ref totalPages, value); HavePages = totalPages > 0; // <--- добавили }
Следующий пункт — номер текущей страницы. С ним всё то же самое, только проверок в сеттере побольше.
int currentPageNo; public int CurrentPageNo { get { return currentPageNo; } set { if (value < 0) throw new ArgumentException(nameof(CurrentPageNo)); if (value >= TotalPages && HavePages) throw new ArgumentException(nameof(CurrentPageNo)); if (value != 0 && !HavePages) throw new ArgumentException(nameof(CurrentPageNo)); Set(ref currentPageNo, value) } }
Теперь, когда текущая страница поменялась, мы должны вычитать новый список студентов. Это нужно сделать в фоне, не блокируя UI-поток. Добавляем в CurrentPageNo
set { if (value < 0) throw new ArgumentException(nameof(CurrentPageNo)); if (value >= TotalPages && HavePages) throw new ArgumentException(nameof(CurrentPageNo)); if (value != 0 && !HavePages) throw new ArgumentException(nameof(CurrentPageNo)); if (Set(ref currentPageNo, value)) // если изменения были... PopulateCurrentPage().IgnoreResult(); // IgnoreResult напишем позже }
Ещё нужно свойство, хранящее список студентов:
IEnumerable currentPage; public IEnumerable CurrentPage { get { return currentPage; } private set { Set(ref currentPage, value); } }
и код, занимающийся обновлением. Он сложный.
// токен для остановки бегущего обновления CancellationTokenSource populationTaskCts = null; async Task PopulateCurrentPage() { // если старое обновление ещё бежит, отменяем его populationTaskCts?.Cancel(); // начиная с этой точки, старое обновление нам не помешает CurrentPage = null; if (!HavePages) return; using (var cts = new CancellationTokenSource()) { // теперь мы - текущее обновление populationTaskCts = cts; var workPageNo = CurrentPageNo; // может поменяться в процессе
try { // асинхронно получаем модельный список var modelPage = await Task.Run(() => GetStudentsFromModel(workPageNo, cts.Token), cts.Token); if (cts.IsCancellationRequested) return; // создаём VM-объекты в UI-потоке var vmPage = modelPage.Select(p => new StudentVM(p.Name, p.ImageUri)) .ToList(); if (cts.IsCancellationRequested) return; // если мы оказались тут, то мы всё ещё текущее обновление Debug.Assert(workPageNo == CurrentPageNo); CurrentPage = vmPage; // и можем присвоить результат } catch (OperationCanceledException) when (cts.IsCancellationRequested) { // ничего не делаем, нас отменили } finally { // если мы текущее обновление, убираем за собой токен if (cts == populationTaskCts) populationTaskCts = null; // в противном случае там чужой токен, его не трогаем } } }
Нам ещё нужен экземпляр модели и процедура GetStudentsFromModel, которая будет их вычитывать:
StudentListModel model = new StudentListModel();
const int pageSize = 10; IEnumerable GetStudentsFromModel(int pageNo, CancellationToken ct) { // make sure it's materialized on background thread return model.GetAllStudents().Skip(pageNo * pageSize).Take(pageSize).ToList(); }
К ней ещё понадобится (позже) подсчёт количества страниц:
int GetPageCount(CancellationToken ct) { var studentCount = model.GetAllStudents().Count(); return (int)Math.Ceiling((double)studentCount / pageSize); }
Сейчас эти процедуры синхронные, но если ваша модель будет выставлять async-интерфейс (как это делает новый EF), их нужно будет сделать асинхронными.
Далее. Нам нужна команда изменения текущей страницы.
public ICommand RequestPageChange { get; }
И её имплементация: метод, который будет отрабатывать:
void OnPageChangeRequest(int newPage) { if (!HavePages) return; if (newPage < 0 || newPage >= TotalPages) return; // log an error? CurrentPageNo = newPage; }
Привязывать команду к имплементации придётся в конструкторе. Заодно и заведём его:
public StudentListVM() { RequestPageChange = new RelayCommand(OnPageChangeRequest); }
(класс RelayCommand есть практически везде, я приведу его позже).
Да, нам ещё в конструкторе нужно запустить подсчёт количества страниц и первоначальное заполнение:
public StudentListVM() { RequestPageChange = new RelayCommand(OnPageChangeRequest); StartInitialPopulate().IgnoreResult(); }
async Task StartInitialPopulate() { TotalPages = await Task.Run(() => GetPageCount(CancellationToken.None)); currentPageNo = 0; await PopulateCurrentPage(); } }
Кажется, ничего не забыли. Класс получился слишком большой, возможно, имеет смысл разрефакторить его на мелкие куски.
Ещё у нас тут упоминался RelayCommand
public class RelayCommand : ICommand { public RelayCommand(Action onExecute) { OnExecute = onExecute; }
public event EventHandler CanExecuteChanged; public bool CanExecute(object parameter) => true; public void Execute(object parameter) => OnExecute((T)parameter);
readonly Action OnExecute; }
и функция IgnoreResult
public static class TaskExtgensions { public static void IgnoreResult(this Task t) { } }
На этом с вью-моделью, кажется, всё, перейдём к представлению.
Вью-модель прикрепляем к представлению, как описано здесь
App.xaml:

App.xaml.cs:
using System.Windows;
using Pages.View; using Pages.VM;
namespace Pages { public partial class App : Application { StudentListVM mainVM = new StudentListVM();
protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); new MainWindow() { DataContext = mainVM }.Show(); } } }
Теперь идём в MainWindow.xaml. Начинаем:

На нашем мок-апе есть явно две области: нижняя маленькая со списком страниц, и верхняя большая с содержимым текущей страницы. Это напрашивается на Grid

Теперь, верхний элемент. Он показывает список элементов, без текущего элемента и всего такого. Так что это ItemsControl

Теперь, как расположены элементы в нём? Они идут не в столбик, а «перетекают» по горизонтали в следующую строку. Отлично, значит, нам нужно установить в ItemsPanel подходящий контейнер:

Как выглядит отдельный элемент? Это не стандартный ToString(), это текст с картинкой. Напишем для этого ItemTemplate

Вроде бы ничего не забыли.

Окей, с таблицей справились, теперь нам нужен список страниц. Откуда его взять? У нас по идее есть только количество страниц, нужно его превратить в список при помощи конвертера. Затем, мы ведь не хотим показывать все страницы? Их может быть очень много. Будем показывать первые страницы, последние страницы и страницы вокруг текущей.
Засучим рукава и вперёд!
Для начала, тип для описания страницы или заполнителя:
enum PageEntryType { Normal, Current, Ellipsis }
struct PageEntry { public int PageNumber { get; } public PageEntryType Type { get; } public PageEntry(int num, PageEntryType type) { PageNumber = num; Type = type; } }
Ну и сам конвертер. Надеюсь, я не напутал с вычислениями.
Нам нужен IMultiValueConverter, потому что у нас два входных значения.
class PageListConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { int currentPage = (int)values[0]; int numberOfPages = (int)values[1]; return RecalcList(currentPage, numberOfPages); }
IEnumerable RecalcList(int currentPage, int numberOfPages) { const int pagesAroundCurrent = 3; // сколько страниц вокруг текущей const int pagesAroundEnd = 2; // сколько страниц по краям
var min = Math.Max(0, currentPage - pagesAroundCurrent); var max = Math.Min(numberOfPages - 1, currentPage + pagesAroundCurrent);
// перекрывается список вокруг текущей страницы со списком у левого края? bool separateLeftEnd = pagesAroundEnd + 1 < min; if (!separateLeftEnd) min = 0;
// перекрывается список вокруг текущей страницы со списком у правого края? bool separateRightEnd = numberOfPages - 1 - pagesAroundEnd - 1 > max; if (!separateRightEnd) max = numberOfPages - 1;
if (separateLeftEnd) { for (int n = 0; n < pagesAroundEnd; n++) yield return new PageEntry(n, PageEntryType.Normal); // между списками разрыв - многоточие yield return new PageEntry(-1, PageEntryType.Ellipsis); }
for (int n = min; n <= max; n++) yield return new PageEntry(n, (n == currentPage) ? PageEntryType.Current : PageEntryType.Normal);
if (separateRightEnd) { // между списками разрыв - многоточие yield return new PageEntry(-1, PageEntryType.Ellipsis); for (int n = numberOfPages - pagesAroundEnd; n < numberOfPages; n++) yield return new PageEntry(n, PageEntryType.Normal); } }
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotSupportedException(); } }
Отлично, список у нас есть, теперь его нужно показать. Как показывать список? Возвращаемся к нашему недописанному XAML'у. Нам нужен ItemsControl, как обычно. Кладём его во вторую строку и добавим небольшой маргин.

В ресурсы положим конвертер

а элементы берём из количества страниц и номера текущей, с использованием этого конвертера:

Окей, дальше нам нужно эти элементы расположить по горизонтали. Для этого сгодится StackPanel. Саму панель тоже центрируем:

Теперь отображение каждого элемента списка. У нас есть три варианта отображения: обыкновенная страница показывается в виде ссылки, текущая — без ссылки, но жирным шрифтом, а на месте для многоточия должно появиться многоточие. Выбор из этих трёх вариантов делаем при помощи привязки Visibility блока к значению нашего элемента через конвертер. Конвертер мы напишем позже, он будет делать видимым только один из трёх нужных элементов. Сам конвертер мы добавим в ресурсы ItemsControl'а:

Продолжаем:

Располагаем элементы один на другом, видимым будет только один.

Первый вариант: нормальное отображение. Нам нужна гиперссылка. По нажатию на неё вызовем обработчик из code-behind.

Второй вариант: текущая страница. Вместо ссылки — TextBlock с жирным шрифтом.

Ну и третий вариант — просто многоточие.

Вот и всё с отображением элемента.

Больше в XAML'е делать нечего.

Заимплементируем обработчик клика по ссылке. Он лежит в классе MainWindow
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); }
void OnPageChangeRequest(object sender, RoutedEventArgs e) {
Нам нужно где-то взять команду. Чтобы не лазить по DataContext'у, сделаем простой трюк: привяжем эту команду в свободное свойство. Например, в Tag. (Это мы сделаем потом, снова-таки в XAML'е.)
var command = (ICommand)Tag; // прочитали оттуда команду if (command == null) return;
var hyperlink = (Hyperlink)sender;
Номер страницы можно вытащить из DataContext'а гиперссылки.
var pageNo = ((PageEntry)hyperlink.DataContext).PageNumber; command.Execute(pageNo); } }
Возвращаемся в XAML и дописываем Tag

Последняя недописанная вещь — PageEntryTypeToVisibilityConverter. Он очень простой:
public class PageEntryTypeToVisibilityConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { PageEntryType expectedType = (PageEntryType)parameter; PageEntryType actualType = ((PageEntry)value).Type; return expectedType == actualType ? Visibility.Visible : Visibility.Collapsed; }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotSupportedException(); } }
Запускаем и получаем:


Да, а давайте ещё сделаем так, чтобы при пустом списке нам показывалось какое-то разумное сообщение. Это просто. Возвращаемся в XAML и добавляем ещё один элемент во внешний Grid

Но так этот элемент будет виден всегда, а нам нужно только когда страниц нету. Не проблема, привяжемся к свойству HavePages. Нам нужно сконвертировать его в Visibility. Но стандартный BooleanToVisibilityConverter конвертирует не в ту сторону, в которую нам надо, поэтому украдём конвертер отсюда
public class BooleanConverter : IValueConverter { public BooleanConverter(T trueValue, T falseValue) { OnTrue = trueValue; OnFalse = falseValue; }
public T OnTrue { get; set; } public T OnFalse { get; set; }
public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (!(value is bool)) return DependencyProperty.UnsetValue; return ((bool)value) ? OnTrue : OnFalse; }
public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotSupportedException(); } }
public class BooleanToVisibilityConverter : BooleanConverter { public BooleanToVisibilityConverter() : base(Visibility.Visible, Visibility.Collapsed) {} }
Теперь можно написать так:

Нужно только положить его в ресурсы окна:

Дальнейшее наведение красоты — на вас.

Можете объяснить суть invoke

Есть основной поток, в котором я создаю все Controls в winforms. И есть второй поток, который вызывается через срабатывание события. В результате этого события мне нужно изменить значение DataSource у DataGridView. В результате возникает ошибка, что доступ к контролу пытается получить не основной поток, в котором был создан контрол. Решением является использование методов Invoke\BeginInvoke. Но саму суть я не понимаю этих методов и как их реализовать в коде. В создаваемом втором потоке используется функция доступа к dgv
private void RefreshTables() { try { if(con.State == ConnectionState.Closed) { con.Open(); } sql = "select rowid, * from OpenPos"; adapOpenPos = new SQLiteDataAdapter(sql, con); dsOpenPos = new DataSet(); adapOpenPos.Fill(dsOpenPos); dataGridView1.DataSource = dsOpenPos.Tables[0]; dataGridView1.Columns[0].Visible = false; dataGridView1.Columns[15].Visible = false; dataGridView1.Update(); con.Close();
if (con.State == ConnectionState.Closed) { con.Open(); } sql = "select rowid, * from ClosePos"; adapClosePos = new SQLiteDataAdapter(sql, con); dsClosePos = new DataSet(); adapClosePos.Fill(dsClosePos); dataGridView2.DataSource = dsClosePos.Tables[0]; dataGridView2.Columns[0].Visible = false; dataGridView2.Columns[14].Visible = false; dataGridView2.Update(); con.Close(); } catch (Exception e) { MessageBox.Show(e.Message); } }


Ответ

Суть метода Invoke довольно проста - он принимает делегат и выполняет его в том потоке, в котором был создан элемент управления, у которого вызывается Invoke. Как вы могли заметить, если обращаться к контролам в WinForms не из того потока, в котором они были созданы, будет выброшено исключение. Соответственно, метод Invoke полезен в случаях, когда необходимо работать с контролом из других потоков. Метод BeginInvoke делает то же самое, но асинхронно.
Небольшой пример использования Invoke
private void ButtonInvoke_Click(object sender, EventArgs e) { var myThread = new Thread(ThreadFunction); myThread.Start(); //метод выполняется в другом потоке }
private void ThreadFunction() { Thread.Sleep(1000); Action action = () => listBox1.Items.Add("value"); // Свойство InvokeRequired указывает, нeжно ли обращаться к контролу с помощью Invoke if (InvokeRequired) Invoke(action); else action(); }
Стоит также отметить, что async/await, добавленные в C# 5, позволяют обойтись без Invoke
private async void ButtonAsync_Click(object sender, EventArgs e) { listBox1.Items.Add("first"); await Task.Run(async () => { await Task.Delay(1000); }); // этот код будет продолжен в UI потоке, // и здесь нет необходимости использовать Invoke listBox1.Items.Add("second"); }

Как можно оценить суровость выполненного запроса?

Например, есть некоторый сервис, который время от времени выполняет запросы и нужно определить насколько запрос был "тяжелым".
По каким критериям это можно оценить?
Первое, что пришло на ум- это замерить загрузку процессора на SQL Server до и во время выполнения запроса.
По каким критериям еще можно понять, что запрос оказался тяжелым и мог негавтивно сказаться на работу других пользователей с БД?
Собственно, это нужно для программы балансировки нагрузки, что бы выполнять тяжелые запросы в наиболее подходящее время. Собственно, не помешал бы минимальный пример, как эту статистику можно получить из когда программы.


Ответ

Тяжесть зависит от плана выполнения. План выполнения может меняться в зависимости от статистики по таблицам. Статистика может устаревать и порождать неверные планы. Так что "тяжесть" - величина непостоянная.
Кроме того, в SQL Server есть кэш данных, и запрос, выбирающий данные их кэша, ес-но будет выдавать меньше физических чтений с диска. Поэтому при замерах стоит смотреть на количество логических чтений, а не только на количество физических.
Грубо можно оценить, замерив время и IO напрямую:
SET STATISTICS TIME ON SET STATISTICS IO ON
запрос
Выполнить несколько раз, отбросить максимальное и минимальное значение по CPU, по остальным - взять среднее.
Показатели IO будут зависеть от плана, так что они при последовательных выполнениях не поменяются. Для воспроизведения промаха кэша - почистите его вызовом CHECKPOINT; DBCC DROPCLEANBUFFERS;
Из .NET значения статистики можно получить, выставлением свойства sqlConnection.StatisticsEnabled = true + вызовом sqlConnection.RetrieveStatistics()

Что делать с "плохим" запросом - достаточно обширный вопрос. Минимальный набор действий
пересчитать статистику для используемых таблиц(обязательно, иначе есть шанс бороть несуществующую проблему!) получить свежий план выполнения если план плохой - оптимизировать запрос, индексы или структуру базы
Что точно не стоит делать - это править SQL наугад. Оптимизация SQL - достаточно простой процесс, но при этом нужно точно представлять механизм выполнения запросов SQL Server, иначе можно получить кучу скрытых проблем :)