Занимаюсь разработкой почтового клиента под ОС Андроид с помощью C# и Xamarin Forms. Работа с сервером и протокол реализован, однако при создании GUI возникла проблема со скоростью подгрузки сообщений. Опыта разработки асинхронных и многопоточных приложений у меня нет, что как раз и мешает мне разобраться с этой проблемой. Листинг метода подргузки сообщений:
private async Task<List<ImapMessageInfo>> LoadMessagesInfo()
{
foreach (var item in uidsCash)
{
await Task.Run(() =>
{
var header = streamOperator.ReceiveResponse($"$ UID FETCH {item} BODY[HEADER]", s => s.Contains("$ OK"))
.Where(n => !n.StartsWith("* ") && !n.StartsWith("$ ") && !(n == ")")).ToList();
if (header.FirstOrDefault().StartsWith("$ OK"))
//throw new InvalidDataException($"Message #{item} was removed or not created");
return;
var body = streamOperator.ReceiveResponse($"$ UID FETCH {item} BODY[TEXT]", s => s.Contains("$ OK"))
.Where(n => !n.StartsWith("* ") && !n.StartsWith("$ ") && !(n == ")")).ToList();
var subject = streamOperator
.ReceiveResponse($"$ UID FETCH {item} BODY[HEADER.FIELDS (Subject)]", s => s.Contains("$ OK"))
.Where(n => !n.StartsWith("* ") && !n.StartsWith("$ ") && !(n == ")")).ToList();
MessageModel msg = ParseBodyHeader(item, header, string.Join("\r\n", subject));
ImapMessageInfo info = new ImapMessageInfo(msg)
{
Flags = GetFlags(item)
};
info.Message.TextHtml = this.LoadText(header, body, "text/html");
info.Message.TextPlain = LoadText(header, body, "text/plain");
messagesCash.Add(info);
});
}
return messagesCash.OrderByDescending(s => int.Parse(s.Message.Uid)).ToList();
}
Данный код позволяет не зависать графическому интерфейсу, однако сам процесс загрузки никак не ускоряет - всего 10 сообщений загружается примерно за 10 секунд. Есть ли возможность ускорить этот процесс до приемлимого для приложения времени?
Обращение к серверу происходит в методе streamOperator.ReceiveResponse
UPD:
В моем коде есть один стрим для чтения и записи файла с сервера. При многопоточном его использовании возникает исключение Mono.Security.Protocol.Tls.TlsException: Bad record MAC
. Как сделать обращение к серверу многопоточным? Нужно ли для каждого потока создавать свой стрим, чтобы читать данные с сервера одновременно в нескольких потоках?
UPD2: Я обновил свой код в соответствии с предложениями в комментариях.
private async Task<List<ImapMessageInfo>> LoadMessagesInfo()
{
var tasks = uidsCash.Select(item => Task.Run (async () =>
{
TcpClient tcpClient = new TcpClient("imap.gmail.com", 993);
SslStream ssl = new SslStream(tcpClient.GetStream());
ssl.AuthenticateAsClient("imap.gmail.com");
StreamOperator streamOperator1 = new StreamOperator(ssl);
await streamOperator1.ReceiveResponse("", s => true);
await streamOperator1.ReceiveResponse($"$ LOGIN {login} {password}", s => s.Contains("$ OK"));
await streamOperator1.ReceiveResponse("$ SELECT INBOX", s => s.Contains("$ OK"));
var headerAsync = await streamOperator1.ReceiveResponse($"$ UID FETCH {item} BODY[HEADER]", s => s.Contains("$ OK"));
var header = headerAsync.Where(n => !n.StartsWith("* ") && !n.StartsWith("$ ") && !(n == ")")).ToList();
if (header.FirstOrDefault().StartsWith("$ OK"))
//throw new InvalidDataException($"Message #{item} was removed or not created");
return;
var bodyAsync = await streamOperator1.ReceiveResponse($"$ UID FETCH {item} BODY[TEXT]", s => s.Contains("$ OK"));
var body = bodyAsync
.Where(n => !n.StartsWith("* ") && !n.StartsWith("$ ") && !(n == ")")).ToList();
var subjectAsync = await streamOperator1
.ReceiveResponse($"$ UID FETCH {item} BODY[HEADER.FIELDS (Subject)]", s => s.Contains("$ OK"));
var subject = subjectAsync
.Where(n => !n.StartsWith("* ") && !n.StartsWith("$ ") && !(n == ")")).ToList();
MessageModel msg = ParseBodyHeader(item, header, string.Join("\r\n", subject));
ImapMessageInfo info = new ImapMessageInfo(msg)
{
Flags = GetFlags(item)
};
info.Message.TextHtml = this.LoadText(header, body, "text/html");
info.Message.TextPlain = LoadText(header, body, "text/plain");
lock (o)
messagesCash.Add(info);
}));
await Task.WhenAll(tasks);
//foreach (var item in uidsCash)
//{
//}
return messagesCash.OrderByDescending(s => int.Parse(s.Message.Uid)).ToList();
}
Однако при обращению с сервера даже с разных стримов возникает ошибка NotSupportedException: Вызов метода BeginWrite невозможен, если другая операция write находится в режиме ожидания
. Метод работы с сервером:
public async Task<List<string>> ReceiveResponse(string query, Func<string, bool> isLastLine)
{
try
{
if(query != string.Empty)
{
byte[] buffer = Encoding.ASCII.GetBytes(query + "\r\n");
await stream.WriteAsync(buffer, 0, buffer.Length);
}
}
catch
{
throw;
}
try
{
StringBuilder sb = new StringBuilder();
string @string;
do
{
byte[] buffer = new byte[2048];
int bytesGot = await stream.ReadAsync(buffer, 0, buffer.Length);
if (bytesGot == 0)
{
throw new EndOfStreamException("Error while reading");
}
@string = Encoding.UTF8.GetString(buffer).Replace("\0", string.Empty);
sb.Append(@string);
} while (!isLastLine(@string));
return sb.ToString().Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries).ToList();
}
catch
{
throw;
}
}
Вот так пробую вызывать всю эту систему:
var resp = Task.Run(() => imap.LoadMessages());
Task.WaitAny(resp);
var from = resp.Result;
Метод LoadMessages вызывает LoadMessageInfo:
public async Task<List<ImapMessageInfo>> LoadMessages(/*DateTime since*/)
{
if (this.messagesCash != null)
return this.messagesCash;
messagesCash = new List<ImapMessageInfo>();
if (uidsCash == null)
uidsCash = await this.GetExistingUids();
return await LoadMessagesInfo();
}
Вы запускаете таски одну за другой, то есть пока текущая задача не кончилась, новая не начнется.
Попробуйте запустить их параллельно, например:
private async Task<List<ImapMessageInfo>> LoadMessagesInfo()
{
var tasks = uidsCash.Select(item => Task.Run(() => {/*..Ваш код..*/})).ToArray();
await Task.WhenAll(tasks);
return messagesCash.OrderByDescending(s => int.Parse(s.Message.Uid)).ToList();
}
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Какие существуют виды рекламных бордов и как выбрать подходящий?
При использовании Facebook API проблем не возникло - я просто передавал необходимые запросы в готовые методы, и метод возвращал jsonobject который я конвертил...
Нужно в конструкторе присвоить полям класса определенные значения и реализовать выбор присваиваемого значения на основе данных пользовательского...
Как в Windows Forms организовать ввод данных из нового окна? При этом, нужно, чтобы введенные данные после нажатия кнопки, допустим "ОК", выводились...
Появилась необходимость переносной проги, без использования Managment StudioВроде как создал локальную бд с типом